还是打上转载吧,毕竟很多都是shadowice1984的东西,只是我用了我看得懂的语言(外星语言?)解释了一遍。ZFY:震惊,机房猴子不会普通话
前言
这道题目真的难!!!才紫的!!!
参考文献
总体框架:https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p3781
看懂DDP那一块的东西:https://blog.csdn.net/weixin_30258901/article/details/99187239
例题
术语
u . l i g h t s o n u.lightson u.lightson, u u u的轻儿子。 u . h e a v y s o n u.heavyson u.heavyson, u u u的重儿子。
思路
老老实实抄袭了,这个题解我是真的佛了。
朴素DP
设 d p i , j dp_{i,j} dpi,j表示最高点为 i i i异或和为 j j j的联通块个数。
那么如果询问为 k k k的话我们最后求的就是: ∑ i = 1 n d p i , k \sum\limits_{i=1}^ndp_{i,k} i=1∑ndpi,k
推转移方程:
考虑现在往
d
p
u
,
k
dp_{u,k}
dpu,k加入子树
d
p
v
dp_{v}
dpv
d
p
u
,
k
+
=
∑
i
⊕
j
=
=
k
d
p
u
,
i
d
p
v
,
j
dp_{u,k}+=\sum\limits_{i⊕j==k}dp_{u,i}dp_{v,j}
dpu,k+=i⊕j==k∑dpu,idpv,j
后面的不就是异或卷积吗?FWT套上。
然后我们设
D
p
i
Dp_{i}
Dpi为
d
p
i
dp_{i}
dpi经过FWT摧残变换后的数组。(注意,基本上后面的值都是FWT向量了。)
所以就是: D p u , k = D p u , k D p v , k + D p u , k Dp_{u,k}=Dp_{u,k}Dp_{v,k}+Dp_{u,k} Dpu,k=Dpu,kDpv,k+Dpu,k。
就是:
D p u , k ∗ = ( D p v , k + 1 ) Dp_{u,k}*=(Dp_{v,k}+1) Dpu,k∗=(Dpv,k+1)
DDP加速
然后我们假如设(这里的
1
1
1表示的是全部位置都是
1
1
1的一个数组,
F
,
f
F,f
F,f也是个数组):
F
u
=
∏
v
∈
u
.
s
o
n
(
F
u
+
1
)
F_{u}=\prod\limits_{v∈u.son}(F_{u}+1)
Fu=v∈u.son∏(Fu+1)
f
u
=
∏
v
∈
u
.
l
i
g
h
t
s
o
n
(
F
u
+
1
)
f_{u}=\prod\limits_{v∈u.lightson}(F_{u}+1)
fu=v∈u.lightson∏(Fu+1)
其实这里的 F F F就是上面的 D p Dp Dp数组,简写。
再设 G G G数组表示一个节点子树内的答案, g g g表示一个节点轻儿子内的答案
g x = ∑ y ∈ x . l i g h t s o n G y g_{x}=\sum\limits_{y∈x.lightson}G_{y} gx=y∈x.lightson∑Gy
怎么转移呢?
y
=
x
.
h
e
a
v
y
s
o
n
y=x.heavyson
y=x.heavyson
F
x
=
f
x
∗
(
F
y
+
1
)
F_{x}=f_{x}*(F_{y}+1)
Fx=fx∗(Fy+1)
G
x
=
g
x
+
F
x
+
G
y
G_{x}=g_{x}+F_{x}+G_{y}
Gx=gx+Fx+Gy
拆开就是:
F
x
=
f
x
+
f
x
∗
F
x
F_{x}=f_{x}+f_{x}*F_{x}
Fx=fx+fx∗Fx
G
x
=
g
x
+
f
x
∗
F
y
+
f
x
+
G
y
G_{x}=g_{x}+f_{x}*F_{y}+f_{x}+G_{y}
Gx=gx+fx∗Fy+fx+Gy
写成矩阵就是:
[
F
y
G
y
1
]
∗
[
f
x
f
x
0
0
1
0
f
x
f
x
+
g
x
1
]
=
[
F
x
G
x
1
]
\begin{bmatrix}F_{y} & G_{y} & 1\end{bmatrix}*\begin{bmatrix}f_{x} & f_{x} & 0 \\ 0 & 1 & 0\\ f_{x} & f_{x}+g_{x} & 1\end{bmatrix}=\begin{bmatrix}F_{x} & G_{x} & 1\end{bmatrix}
[FyGy1]∗⎣⎡fx0fxfx1fx+gx001⎦⎤=[FxGx1]
但是这个矩阵特别难维护。
怎么搞?
奆佬猫神给了我们一个神仙的维护方法:
[ a b 0 0 1 0 c d 1 ] ∗ [ A B 0 0 1 0 C D 1 ] = [ a A a B + b 0 0 1 0 c A + C c B + d + D 1 ] \begin{bmatrix}a & b & 0 \\ 0 & 1 & 0\\ c & d & 1\end{bmatrix}*\begin{bmatrix}A & B & 0 \\ 0 & 1 & 0\\ C & D & 1\end{bmatrix}=\begin{bmatrix}aA & aB+b & 0 \\ 0 & 1 & 0\\ cA+C & cB+d+D & 1\end{bmatrix} ⎣⎡a0cb1d001⎦⎤∗⎣⎡A0CB1D001⎦⎤=⎣⎡aA0cA+CaB+b1cB+d+D001⎦⎤
维护四个向量就可以啦!!!
当然也许有人会问单位矩阵是什么,又没有改变矩阵乘法的方式,不就是: a = 1 , b = c = d = 0 a=1,b=c=d=0 a=1,b=c=d=0。
细节
关于修改是如何逆回去的
这里有模运算,可以大量的逆元乘法,但是也是因为模运算,导致有可能乘的是0,出现了乘 0 0 0除 0 0 0的情况,那么我们就只能坐以待毙了吗,其实不然,在维护 f f f的时候,我们可以顺带记录乘了多少个 0 0 0,实现不可实现的除以 0 0 0的操作。
点值都去哪了?
这里解释一下,点值直接在 F W T FWT FWT之后,直接赋给了 f f f,自己看看就会发现不是很影响。
矩阵的答案以及如何从轻儿子的矩阵抠东西下来
我们知道了一个矩阵,我们又要怎么把他的答案抠下来?
其实就是乘一个 [ 0 0 1 ] \begin{bmatrix}0 & 0 & 1\end{bmatrix} [001]就全部抠下来了。
轻儿子你也可以看作乘了 [ 0 0 1 ] \begin{bmatrix}0 & 0 & 1\end{bmatrix} [001]再抠。
代码
时间复杂度: O ( m q log n ) O(mq\log{n}) O(mqlogn)
#include<cstdio>
#include<cstring>
#define N 31000
#define NN 61000
#define M 140
#define mod 10007
using namespace std;
int inv[N];
int lim,n;
struct node
{
int y,next;
}tr[NN];int last[N],len;
inline void inz(int x,int y){len++;tr[len].y=y;tr[len].next=last[x];last[x]=len;}
inline void fwt(int *a,const int& o)
{
for(register int i=2;i<=lim;i<<=1)
{
int mid=(i>>1);
for(register int j=0;j<lim;j+=i)
{
for(register int k=0;k<mid;k++)
{
int x=a[j+k],y=a[j+k+mid];
a[j+k]=(x+y)*o%mod;a[j+k+mid]=(x-y+mod)*o%mod;//当初为了减轻常数抄了题解
}
}
}
}//FWT
struct poly
{
int a[M];
inline int& operator[](const int& x){return a[x];}
}we[N],one,L2;
inline int pdz(int x){return x>mod?x-mod:x;}
inline int pdf(int x){return x<0?x+mod:x;}
inline poly operator*(poly x,poly y){for(register int i=0;i<lim;i++)L2[i]=(x[i]*y[i])%mod;return L2;}
inline poly operator+(poly x,poly y){for(register int i=0;i<lim;i++)L2[i]=pdz(x[i]+y[i]);return L2;}
inline poly operator-(poly x,poly y){for(register int i=0;i<lim;i++)L2[i]=pdf(x[i]-y[i]);return L2;}
inline poly operator/(poly x,poly y){for(register int i=0;i<lim;i++)L2[i]=L2[i]*(inv[y[i]])%mod;return L2;}
inline void operator+=(poly &x,poly y){for(register int i=0;i<lim;i++)x[i]=pdz(x[i]+y[i]);}
inline void operator-=(poly &x,poly y){for(register int i=0;i<lim;i++)x[i]=pdf(x[i]-y[i]);}
inline void operator*=(poly &x,poly y){for(register int i=0;i<lim;i++)x[i]=(x[i]*y[i])%mod;}
inline void operator/=(poly &x,poly y){for(register int i=0;i<lim;i++)x[i]=x[i]*(inv[y[i]])%mod;}
struct Mar
{
poly a[2][2];
poly* operator[](const int& x){return a[x];}
}L1;//矩阵
inline Mar operator*(Mar a,Mar b)
{
L1[0][0]=a[0][0]*b[0][0];
L1[0][1]=a[0][0]*b[0][1]+a[0][1];
L1[1][0]=a[1][0]*b[0][0]+b[1][0];
L1[1][1]=a[1][0]*b[0][1]+a[1][1]+b[1][1];
return L1;
}
struct data//维护f的工具。
{
int fz[M];//有多少个0
int fv[M];//除了0以外的乘积,没有默认为1
inline void wt(const int x,const int k){if(k==0)fz[x]=fv[x]=1;else fz[x]=0,fv[x]=k;} //初始化所用到的函数
inline void operator*=(poly y){for(int i=0;i<lim;i++)y[i]%mod==0?fz[i]+=1:fv[i]=(fv[i]*y[i])%mod;}
inline void operator/=(poly y){for(int i=0;i<lim;i++)y[i]%mod==0?fz[i]-=1:fv[i]=(fv[i]*inv[y[i]])%mod;}
};
inline void cpy(poly &x,const data y){for(int i=0;i<lim;i++)x[i]=(!y.fz[i])?y.fv[i]:0;}//将f复制到矩阵上面的一个向量上
struct Tree
{
int son[N][2],h[N],size[N],fa[N];
data lt[N];/*f*/poly lh[N];/*g*/
Mar val[N]/*BST上所管理的值*/,w[N]/*自身的值*/;
bool v[N];//用来建全局平衡二叉树的
inline void update(const int x){val[x]=val[son[x][0]]*w[x]*val[son[x][1]];}//维护
inline void ins(const int x,const int y){lt[x]*=(val[y][1][0]+one);lh[x]+=val[y][1][1];}//将一个点插入到x中
inline void del(const int x,const int y){lt[x]/=(val[y][1][0]+one);lh[x]-=val[y][1][1];}//将一个点删除出x
inline bool pd(const int x){return son[fa[x]][0]!=x && son[fa[x]][1]!=x;}//判断是不是真正的二组
inline void pu(const int x){cpy(w[x][0][0],lt[x]);w[x][0][1]=w[x][1][0]=w[x][0][0];w[x][1][1]=lh[x]+w[x][0][0];}//将一个点的f,g打到矩阵上面去
int sta[N],top;
int root;poly ans;
int bt(int l,int r)
{
if(l>r)return 0;
int sum=0;
for(register int i=l;i<=r;i++)sum+=size[sta[i]];
int now=size[sta[l]];
for(register int i=l;i<=r;i++,now+=size[sta[i]])
{
int x=sta[i];
if(now*2>=sum)
{
int ls=bt(i+1,r),rs=bt(l,i-1);fa[ls]=fa[rs]=x;
son[x][0]=ls;son[x][1]=rs;
update(x);
return x;
}
}
}
int sbuild(int x)
{
for(register int i=x;i;i=h[i])v[i]=1;
for(register int i=x;i;i=h[i])
{
for(int k=last[i];k;k=tr[k].next)
{
int y=tr[k].y;
if(!v[y])
{
int z=sbuild(y);fa[z]=i;
ins(i,z);
}
}
}
top=0;for(register int i=x;i;i=h[i])pu(i),sta[++top]=i,size[i]-=size[h[i]];
return bt(1,top);
}
void dfs(int x,const int fa)
{
size[x]=1;
for(register int k=last[x];k;k=tr[k].next)
{
int y=tr[k].y;
if(fa!=y)
{
dfs(y,x);
size[x]+=size[y];
if(size[y]>size[h[x]])h[x]=y;
}
}
}
//建树部分
inline void modify(int x,int k)
{
lt[x]/=we[x];
for(register int i=0;i<lim;i++)we[x][i]=0;we[x][k]=1;
fwt(we[x].a,1);
lt[x]*=we[x];pu(x);
//先删后加
for(register int i=x;i;i=fa[i])
{
if(pd(i) && fa[i]){del(fa[i],i);update(i);ins(fa[i],i);pu(fa[i]);}
else update(i);
}//沿着重链或者轻链慢慢往上跳
}
inline void getans()
{
ans=val[root][1][1];//答案
fwt(ans.a,5004);
}
}T;
int main()
{
inv[1]=1;for(register int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;//线性推逆元
scanf("%d%d",&n,&lim);
for(register int i=0;i<lim;i++)one[i]=1;
for(register int i=1;i<=n;i++){int x;scanf("%d",&x);we[i][x]=1;fwt(we[i].a,1);}
for(register int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
inz(x,y);inz(y,x);
}
T.w[0][0][0]=T.val[0][0][0]=one;//处理成单位矩阵
for(int i=1;i<=n;i++)for(int j=0;j<lim;j++)T.lt[i].wt(j,we[i][j]);//预处理
T.dfs(1,0);T.root=T.sbuild(1);T.getans();
int q;scanf("%d",&q);
for(register int i=1;i<=q;i++)
{
register char opt[10];scanf("%s",opt);
if(opt[0]=='Q')
{
int t;scanf("%d",&t);
printf("%d\n",T.ans[t]);
}
else {int x,k;scanf("%d%d",&x,&k);T.modify(x,k);T.getans();}
}
return 0;
}