概述
题目分析
dalao tql
暴力DP
设 f ( x , k ) f(x,k) f(x,k)表示深度最浅点为 x x x的连通块,价值为 k k k的有多少个。
那么对于 x x x,在遍历儿子前 f ( x , v x ) = 1 f(x,v_x)=1 f(x,vx)=1。对于每个儿子 y y y,都有转移:
f ′ ( x , k ) = f ( x , k ) + ∑ i = 0 m − 1 f ( x , i ) f ( y , k ⊕ i ) f'(x,k)=f(x,k)+sum_{i=0}^{m-1}f(x,i)f(y,k oplus i) f′(x,k)=f(x,k)+i=0∑m−1f(x,i)f(y,k⊕i)
对于一个询问,统计所有 x x x的 f ( x , k ) f(x,k) f(x,k)。
复杂度 O ( Q n m 2 ) O(Qnm^2) O(Qnm2)
加个FWT
发现上面那个式子是个卷积的形式,那么弄个FWT来搞搞。
一开始将所有数组都先FWT一次,那么中间过程的所有卷积操作都变成直接乘法了,最后再FWT回来,复杂度 O ( Q n m ) O(Qnm) O(Qnm)
动态DP
首先按照动态DP的关键先树剖一下,然后就是轻重儿子分开考虑了。
不能对于每次询问,把每个节点的 f ( x , k ) f(x,k) f(x,k)都加起来了,所以设一个 h ( x , k ) h(x,k) h(x,k),表示子树 x x x内每一个节点的 f ( y , k ) f(y,k) f(y,k)的和。
设 l f ( x , k ) = ∏ y ∈ l i g h t s o n x ( 1 + f ( y , k ) ) , l h ( x , k ) = ∑ y ∈ l i g h t s o n x h ( y , k ) lf(x,k)=prod_{y in lightson_x} (1+f(y,k)),lh(x,k)=sum_{y in lightson_x} h(y,k) lf(x,k)=∏y∈lightsonx(1+f(y,k)),lh(x,k)=∑y∈lightsonxh(y,k)
写出生成函数 F ( x ) = ∑ i = 0 m − 1 f ( x , i ) z i F(x)=sum_{i=0}^{m-1} f(x,i)z^i F(x)=∑i=0m−1f(x,i)zi, H ( x ) , l F ( x ) , l H ( x ) H(x),lF(x),lH(x) H(x),lF(x),lH(x)以此类推。
设 x x x的重儿子为 y y y,那么向量 ( F ( y ) , H ( y ) , z 0 ) (F(y),H(y),z^0) (F(y),H(y),z0)转移到 ( F ( x ) , H ( x ) , z 0 ) (F(x),H(x),z^0) (F(x),H(x),z0)是乘了这样一个矩阵:
( l F ( x ) z v x l F ( x ) z v x 0 0 1 0 l F ( x ) z v x l H ( x ) + l F ( x ) z v x 1 ) begin{pmatrix} lF(x) {z^{v_{x}}} & lF(x) {z^{v_{x}}} & 0 \ 0 & 1 & 0 \ lF(x) {z^{v_{x}}} & lH(x) + lF(x) {z^{v_{x}}} & 1 end{pmatrix} ⎝⎛lF(x)zvx0lF(x)zvxlF(x)zvx1lH(x)+lF(x)zvx001⎠⎞
那么接下来的事情就是,用一棵线段树维护一段区间内的转移矩阵的乘积。
询问比较好处理,直接将根节点所在重链的所有转移矩阵乘起来就行了。
修改的话,一路条重链上去,这条重链链顶的 F F F和 H H H改变了,会导致链顶父亲的 l F lF lF和 l H lH lH改变,维护一下即可……呃,等等。
“维护一下即可”?说的轻巧。因为 l F ( x ) = ∏ y ∈ l i g h t s o n x ( F ( y ) + 1 ) lF(x)=prod_{y in lightson_x} (F(y)+1) lF(x)=∏y∈lightsonx(F(y)+1),所以我们先要除以 y y y在 F F F改变前的 F ( y ) + 1 F(y)+1 F(y)+1,再乘以改变后的。可是这个要除的值,可能是0。
于是我们需要用结构体写个特殊的变量来维护 l F ( x ) lF(x) lF(x),这个变量将一个值写成 x × 0 y x times 0^y x×0y的形式。除以0的时候,就直接将 y y y减1即可。
常数优化
正解算法已经出炉了,但是还可以进行常数优化。我们的矩阵是 3 × 3 3 times 3 3×3的,相当于常数为 27 27 27。
但是(式子直接抄的immortalCO大神的):
( a 1 ‾ b 1 ‾ 0 0 1 0 c 1 ‾ d 1 ‾ 1 ) × ( a 2 ‾ b 2 ‾ 0 0 1 0 c 2 ‾ d 2 ‾ 1 ) = ( a 1 a 2 ‾ b 1 + a 1 b 2 ‾ 0 0 1 0 a 2 c 1 + c 2 ‾ b 2 c 1 + d 1 + d 2 ‾ 1 ) begin{pmatrix} underline{a_1} & underline{b_1} & 0 \ 0 & 1 & 0 \ underline{c_1} & underline{d_1} & 1 end{pmatrix} times begin{pmatrix} underline{a_2} & underline{b_2} & 0 \ 0 & 1 & 0 \ underline{c_2} & underline{d_2} & 1 end{pmatrix} = begin{pmatrix} underline{a_1 a_2} & underline{b_1 + a_1 b_2} & 0 \ 0 & 1 & 0 \ underline{a_2 c_1 + c_2} & underline{b_2 c_1 + d_1 + d_2} & 1 end{pmatrix} ⎝⎛a10c1b11d1001⎠⎞×⎝⎛a20c2b21d2001⎠⎞=⎝⎛a1a20a2c1+c2b1+a1b21b2c1+d1+d2001⎠⎞
所以我们只要维护 a , b , c , d a,b,c,d a,b,c,d四个值即可,常数减小到 4 4 4。
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int M=130,inv2=5004,mod=10007,N=30005;
int n,m,Q,tot,tim;
int e[M][M],inv[mod+5],v[N],tmp1[M],tmp2[M];
int h[N],ne[N<<1],to[N<<1];
struct orz{
int x,y;
void put_val(int k) {if(k) x=k,y=0; else x=1,y=1;}
friend orz operator * (orz A,int B) {
if(!B) ++A.y; else A.x=A.x*B%mod;
return A;
}
friend orz operator / (orz A,int B) {
if(!B) --A.y; else A.x=A.x*inv[B]%mod;
return A;
}
int val() {return y?0:x;}
};
int qm(int x) {return x>=mod?x-mod:x;}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void FWT(int *a,int n,int x) {
for(RI i=1;i<n;i<<=1)
for(RI j=0;j<n;j+=(i<<1))
for(RI k=0;k<i;++k) {
int t1=a[j+k],t2=a[j+i+k];
a[j+k]=qm(t1+t2),a[j+i+k]=qm(t1-t2+mod);
if(x==-1) a[j+k]=a[j+k]*inv2%mod,a[j+i+k]=a[j+i+k]*inv2%mod;
}
}
int fa[N],dep[N],top[N],sz[N],pos[N],repos[N],son[N],bot[N];
orz lF[N][M];int F[N][M],H[N][M],lH[N][M];
void dfs1(int x,int las) {
fa[x]=las,dep[x]=dep[las]+1,sz[x]=1;
for(RI i=h[x];i;i=ne[i])
if(to[i]!=las) dfs1(to[i],x),sz[x]+=sz[to[i]];
}
void dfs2(int x,int las) {
int bj=0,mx=0; pos[x]=++tim,repos[tim]=x;
for(RI i=h[x];i;i=ne[i])
if(to[i]!=las&&sz[to[i]]>mx) mx=sz[to[i]],bj=to[i];
if(!bj) {bot[top[x]]=pos[x];return;}
son[x]=bj,top[bj]=top[x],dfs2(bj,x);
for(RI i=h[x];i;i=ne[i])
if(to[i]!=las&&to[i]!=bj) top[to[i]]=to[i],dfs2(to[i],x);
}
void pre_dfs(int x,int las) {
for(RI i=0;i<m;++i) F[x][i]=e[v[x]][i];
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==las) continue;
pre_dfs(to[i],x);
for(RI j=0;j<m;++j) {
F[x][j]=qm(F[x][j]+F[x][j]*F[to[i]][j]%mod);
H[x][j]=qm(H[x][j]+H[to[i]][j]);
}
}
for(RI i=0;i<m;++i) H[x][i]=qm(H[x][i]+F[x][i]);
}
void pre_getl() {
for(RI x=1;x<=n;++x) {
for(RI i=0;i<m;++i) lF[x][i].put_val(e[0][i]),lH[x][i]=0;
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==fa[x]||to[i]==son[x]) continue;
for(RI j=0;j<m;++j) {
lF[x][j]=lF[x][j]*qm(1+F[to[i]][j]);
lH[x][j]=qm(lH[x][j]+H[to[i]][j]);
}
}
}
}
struct tree_node{int a[M],b[M],c[M],d[M];}tr[N<<2];
tree_node operator * (tree_node A,tree_node B) {
tree_node C;
for(RI i=0;i<m;++i) C.a[i]=C.b[i]=C.c[i]=C.d[i]=0;
for(RI i=0;i<m;++i) {
C.a[i]=A.a[i]*B.a[i]%mod;
C.b[i]=qm(A.b[i]+A.a[i]*B.b[i]%mod);
C.c[i]=qm(B.a[i]*A.c[i]%mod+B.c[i]);
C.d[i]=qm(B.b[i]*A.c[i]%mod+qm(A.d[i]+B.d[i]));
}
return C;
}
void update_node(int i,int x) {
for(RI j=0;j<m;++j) {
tr[i].a[j]=tr[i].b[j]=tr[i].c[j]=tr[i].d[j]=lF[x][j].val()*e[v[x]][j]%mod;
tr[i].d[j]=qm(tr[i].d[j]+lH[x][j]);
}
}
void build(int s,int t,int i) {
if(s==t) {update_node(i,repos[s]);return;}
int mid=(s+t)>>1;
build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
tr[i]=tr[(i<<1)|1]*tr[i<<1];//注意合并的顺序QAQ
}
tree_node query(int l,int r,int s,int t,int i) {
if(l<=s&&t<=r) return tr[i];
int mid=(s+t)>>1;
if(r<=mid) return query(l,r,s,mid,i<<1);
if(mid+1<=l) return query(l,r,mid+1,t,(i<<1)|1);
return query(l,r,mid+1,t,(i<<1)|1)*query(l,r,s,mid,i<<1);
}
void chan(int x,int s,int t,int i) {
if(s==t) {update_node(i,repos[s]);return;}
int mid=(s+t)>>1;
if(x<=mid) chan(x,s,mid,i<<1);
else chan(x,mid+1,t,(i<<1)|1);
tr[i]=tr[(i<<1)|1]*tr[i<<1];
}
void getans(int x) {
tree_node re=query(pos[x],bot[x],1,n,1);
for(RI i=0;i<m;++i) tmp1[i]=re.c[i],tmp2[i]=re.d[i];//F,H
}
void walk(int x,int new_v) {
v[x]=new_v;
while(x) {
int y=fa[top[x]];getans(top[x]);
if(y) {
for(RI i=0;i<m;++i)
lF[y][i]=lF[y][i]/qm(tmp1[i]+1),
lH[y][i]=qm(lH[y][i]-tmp2[i]+mod);
}
chan(pos[x],1,n,1),getans(top[x]);
if(y) {
for(RI i=0;i<m;++i)
lF[y][i]=lF[y][i]*qm(tmp1[i]+1),
lH[y][i]=qm(lH[y][i]+tmp2[i]);
}
x=y;
}
}
int main()
{
char op[10];int x,y;
scanf("%d%d",&n,&m);
for(RI i=1;i<=n;++i) scanf("%d",&v[i]);
for(RI i=1;i<n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
for(RI i=0;i<m;++i) e[i][i]=1,FWT(e[i],m,1);
inv[1]=1;for(RI i=2;i<mod;++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
dfs1(1,0),top[1]=1,dfs2(1,0),pre_dfs(1,0),pre_getl(),build(1,n,1);
scanf("%d",&Q);
while(Q--) {
scanf("%s",op);
if(op[0]=='C') scanf("%d%d",&x,&y),walk(x,y);
else {
scanf("%d",&x),getans(1);
FWT(tmp2,m,-1),printf("%dn",tmp2[x]);
}
}
return 0;
}
最后
以上就是健忘红牛为你收集整理的bzoj4911/洛谷P3781 切树游戏 动态DP+FWT题目分析代码的全部内容,希望文章能够帮你解决bzoj4911/洛谷P3781 切树游戏 动态DP+FWT题目分析代码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复