bzoj4911/洛谷P3781 切树游戏 动态DP+FWT

题目分析

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=0m1f(x,i)f(y,ki)

对于一个询问,统计所有 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)=ylightsonx(1+f(y,k)),lh(x,k)=ylightsonxh(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=0m1f(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)=ylightsonx(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("%d\n",tmp2[x]);
		}
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值