A
不难发现一些性质:
- 在最优解中,每个叶子到根的路径上的黑点数,等于最浅的叶子结点的深度。
- 对于任意一个点,都满足:它到子树内任意一个叶子结点的路径上黑点数量都相同。不妨记 f ( i ) f(i) f(i) 表示 i i i 到子树内的叶子节点所经过的黑点数量。
考虑一个树的链剖分,每个点将子树内包含最浅的叶子节点的儿子
作为浅儿子
。
于是就有一个贪心:先根据性质 1 1 1,可以先将根节点往下的浅链
上所有点染黑。然后考虑浅链
上一个点 x x x 的一个深儿子
y y y,根据性质 2 2 2,我们就可以确定它的 f ( y ) f(y) f(y) 值,它等于 x x x 的浅儿子
p p p 的 f ( p ) f(p) f(p)。
知道 f f f 值后,就可以对这个浅儿子
递归求解,显然最优的做法是将它所在的浅链上,最深的 f ( p ) f(p) f(p) 个点染黑(这样影响到的叶子结点最少),然后重复上面的步骤,对下一层浅儿子进行求解。
于是就在 O ( n ) O(n) O(n) 的时间内解决了,代码如下:
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
int n;
vector<int> e[maxn];
int fa[maxn],mson[maxn],dep[maxn],mdep[maxn];
void dfs1(int x){
mdep[x]=1e9;
for(int y:e[x])if(y!=fa[x]){
dep[y]=dep[x]+1;fa[y]=x;dfs1(y);
if(mdep[y]<mdep[mson[x]])mson[x]=y;
mdep[x]=min(mdep[x],mdep[y]);
}
if(e[x].size()==1)mdep[x]=dep[x];
}
int top[maxn],L[maxn];
void dfs2(int x,int tp){
L[tp]++;top[x]=tp;
if(mson[x])dfs2(mson[x],tp);
for(int y:e[x])if(y!=fa[x]&&y!=mson[x])dfs2(y,y);
}
int ans=0;
void solve(int x,int num){
ans+=num;
for(int i=x;i;i=mson[i]){
int p=L[top[i]]-(dep[i]-dep[top[i]]+1);
p=min(p,num);
for(int j:e[i])if(j!=mson[i]&&j!=fa[i])solve(j,p);
}
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y;i<n;i++){
scanf("%d %d",&x,&y);
e[x].push_back(y);e[y].push_back(x);
}
dep[1]=1;mdep[0]=1e9;dfs1(1);dfs2(1,1);
solve(1,L[1]);printf("%d",ans);
}
B
容易发现是数位 dp \text{dp} dp,这种精细的 dp \text{dp} dp 我是最做不来的了,调了好久才能过……
定义一个最暴力的状态,把所有要用的东西加进去: f i , j , r , s , t , 0 / 1 f_{i,j,r,s,t,0/1} fi,j,r,s,t,0/1 表示第 i i i 位为 j j j,前 i i i 位乘 k k k 给后面的进位值为 r r r,前 i i i 位的和为 s s s, x × k x\times k x×k 的前 i i i 位的和为 t t t, 0 / 1 0/1 0/1 表示这个数与 n n n 的大小关系, 0 0 0 表示 x > n x>n x>n, 1 1 1 表示 x ≤ n x\leq n x≤n。
转移的时候枚举第 i + 1 i+1 i+1 位放什么数即可,很容易计算各维的值。
然后这个 19 × 10 × 1000 × 340 × 340 × 2 19\times 10\times1000\times 340\times 340\times 2 19×10×1000×340×340×2 的状态数显然会爆炸,考虑优化一下。
发现我们只关心 s s s 和 t t t 的差值,所以这两维可以缩成一维,直接记录 s − t s-t s−t 的值。
写完代码后发现, j j j 是没有用的,去掉即可。
然后 19 × 1000 × 340 × 2 19\times 1000\times 340\times 2 19×1000×340×2 就可以过啦!代码如下:
#include <cstdio>
#include <assert.h>
#include <algorithm>
using namespace std;
#define ll long long
#define off 200
ll n;int k;
ll f[20][1000][410][2];
//f[i][r][d][0/1]表示前i位,往前进位进了r,x-x*k等于d,此时与n的前i位的大小关系
int c[20],cn=0;
ll ans=0;
int main()
{
scanf("%lld %d",&n,&k);
while(n>0)c[++cn]=n%10,n/=10;
for(int i=0;i<=9;i++)
f[1][i*k/10][i-i*k%10+off][i<=c[1]]++;
for(int i=2;i<=cn;i++){
for(int j=0;j<=9;j++){
for(int r=0;r<1000;r++){
//枚举上一位给过来的进位值
for(int d=0;d<=2*off;d++){
//枚举上一位时的差值
int R=r%10+j*k%10,G=r/10+j*k/10;
if(R>9)R-=10,G++;
int D=d+j-R;
assert(G<1000);
if(D<0||D>2*off)continue;
if(j!=c[i]){
//大小关系改变
f[i][G][D][j<c[i]]+=f[i-1][r][d][0]+f[i-1][r][d][1];
}else{
//大小关系不变
f[i][G][D][0]+=f[i-1][r][d][0];
f[i][G][D][1]+=f[i-1][r][d][1];
}
}
}
}
}
for(int r=0;r<900;r++){
int p=r/100+r/10%10+r%10;
ans+=f[cn][r][p+off][1];
}
printf("%lld",ans-1);
}
C
做法十分巧妙(别问,问就是蒟蒻不会)。
先不管每个位上是 1 1 1 还是 − 1 -1 −1,注意到如果进来了 k k k 个球,那么一定会从下面出去 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor ⌊2k⌋ 个,从右边出去 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor ⌊2k⌋ 个,当 k k k 是奇数时,会有 1 1 1 个球的出去位置与这个位置的值相关。
事实上我们并不关心那出去的 ⌊ k 2 ⌋ \lfloor \frac k 2 \rfloor ⌊2k⌋ 个球是谁,所以递推一下,大部分球的路径就确定下来了,至多只会有 n m nm nm 个球的路径与每个位置的值相关。
然后考虑轮廓线 dp \text{dp} dp,从上往下,从左往右推,记录下轮廓线上每个位置往下推的球数,以及从右边界出去的,并且落到篮子里的球数,每次枚举关键位置是 1 1 1 还是 − 1 -1 −1 转移一下就好了。
据说状态数虽然看起来很多,实际上每个位置不超过 2.7 × 1 0 5 2.7\times 10^5 2.7×105 个,所以大概是能AC的。
代码如下:
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <algorithm>
using namespace std;
#define ll long long
#define mod 998244353
int n,m;ll K;
char sc[20],sl[20];
ll ball[12][12];
const int base=51,hash_val=9904013;
int cur=0,tot[2],last[hash_val+10],pd[hash_val+10],npd=0;
struct par{
int num,ans;
long long Sta;
}sta[2][10000010];
int ne[10000010];
void add(int &x,int y){
x=(x+y>=mod?x+y-mod:x+y);}
void add_sta(long long x,int y,int z){
int tmp=(x+(((ll)y)<<7))%hash_val;
if(pd[tmp]!=npd)pd[tmp]=npd,last[tmp]=0;
for(int i=last[tmp];i;i=ne[i]){
if(sta[cur][i].Sta==x&&sta[cur][i].num==y){
return add(sta[cur][i].ans,z);
}
}
ne[++tot[cur]]=last[tmp];last[tmp]=tot[cur];
sta[cur][tot[cur]]=(par){
y,z,x};
}
ll bin[12];
void trans(int x,int new_ball,par &s){
int b1=s.Sta/bin[x]%base,b2=s.Sta/bin[x+1]%base,b3=b1+b2+new_ball;
if(~b3&1){
add_sta(s.Sta+((b3>>1)-b1)*bin[x]+((b3>>1)-b2)*bin[x+1],s.num,2ll*s.ans%mod);
}else{
ll Sta=s.Sta+((b3>>1)-b1)*bin[x]+((b3+1>>1)-b2)*bin[x+1];
add_sta(Sta,s.num,s.ans);
add_sta(Sta+bin[x]-bin[x+1],s.num,s.ans);
}
}
int w[11*11];
int main()
{
scanf("%d %d %lld",&n,&m,&K);
scanf("%s %s",sc+1,sl+1);
ball[1][1]=K;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
ball[i+1][j]+=ball[i][j]>>1;
ball[i][j+1]+=ball[i][j]>>1;
ball[i][j]&=1;
}
ll pre_ball=0;
for(int i=1;i<=n;i++)if(sc[i]=='1')pre_ball+=ball[i][m+1];
for(int i=1;i<=m;i++)if(sl[i]=='1')pre_ball+=ball[n+1][i];
bin[m+1]=1;for(int i=m;i>=1;i--)bin[i]=bin[i+1]*base;
sta[0][tot[0]=1]=(par){
0,1,0};
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
npd++;cur^=1;tot[cur]=0;
for(int k=1;k<=tot[cur^1];k++)
trans(j,ball[i][j],sta[cur^1][k]);
}
for(int j=1;j<=tot[cur];j++){
if(sc[i]=='1')sta[cur][j].num+=sta[cur][j].Sta%base;
sta[cur][j].Sta/=base;
}
}
for(int i=1;i<=tot[cur];i++){
ll &s=sta[cur][i].Sta;int num=sta[cur][i].num;
for(int j=m;j>=1;j--){
if(sl[j]=='1')num+=s%base;
s/=base;
}
add(w[num+1],sta[cur][i].ans);
}
for(int i=2;i<=n*m+1;i++)add(w[i],w[i-1]);
int q;scanf("%d",&q);while(q--){
ll l,r;scanf("%lld %lld",&l,&r);
if(r<pre_ball||l>pre_ball+n*m){
puts("0");continue;
}
l=max(1ll,l-pre_ball+1);r=min(1ll*n*m+1,r-pre_ball+1);
printf("%d\n",(w[r]-w[l-1]+mod)%mod);
}
}