一、题目
题目描述
给你一棵 n n n个节点的树,每个节点都有一个小于 m m m的权值,定义一棵子树的权值为所有节点的异或和,问权值为 0.. m − 1 0..m−1 0..m−1的所有子树的个数。(这里子树的意思是联通子图)
二、解法
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为第
i
i
i个点异或值为
j
j
j的方案数,枚举子节点,转移如下:
d
p
[
u
]
[
k
]
=
∑
i
⊕
j
=
k
d
p
[
u
]
[
i
]
×
d
p
[
v
]
[
j
]
dp[u][k]=\sum_{i\oplus j=k}dp[u][i]\times dp[v][j]
dp[u][k]=i⊕j=k∑dp[u][i]×dp[v][j]这显然是
fwt
\text{fwt}
fwt的形式,可以用
fwt
\text{fwt}
fwt优化,我们初始化把每个节点的权值那一位赋成
1
1
1,回溯时需要增加异或和
0
0
0的增加
1
1
1(父亲可以不选这个儿子),我们把每个点的
d
p
dp
dp求和最后输出就行了(因为每个点的
d
p
dp
dp值必然选了当前点而且只考虑子树)
#include <cstdio>
#include <cstring>
const int M = 1030;
const int MOD = 1e9+7;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int T,n,m,tot,f[M],dp[M][M],ans[M],inv2=(MOD+1)/2;
struct edge
{
int v,next;
}e[2*M];
void fwt_xor(int *a,int n,int op)
{
for(int i=1;i<n;i<<=1)
for(int p=i<<1,j=0;j<n;j+=p)
for(int k=0;k<i;k++)
{
int x=a[j+k],y=a[i+j+k];
a[j+k]=(x+y)%MOD;
a[i+j+k]=(x+MOD-y)%MOD;
if(op==-1)
{
a[j+k]=1ll*a[j+k]*inv2%MOD;
a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
}
}
}
void dfs(int u,int fa)
{
fwt_xor(dp[u],m,1);
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
fwt_xor(dp[v],m,1);
for(int j=0;j<m;j++)
dp[u][j]=1ll*dp[u][j]*dp[v][j]%MOD;
}
fwt_xor(dp[u],m,-1);
for(int i=0;i<m;i++)
ans[i]=(ans[i]+dp[u][i])%MOD;
dp[u][0]++;
}
signed main()
{
T=read();
while(T--)
{
memset(dp,0,sizeof dp);
memset(ans,0,sizeof ans);
n=read();m=read();
for(int i=1;i<=n;i++)
{
tot=f[i]=0;
dp[i][read()]=1;
}
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
dfs(1,0);
printf("%d",ans[0]);
for(int i=1;i<m;i++)
printf(" %d",ans[i]);
puts("");
}
}