题目描述:
给你一棵n个节点的带点权树。每个点的点权都小于m,m为某个2的整次方数,求树上所有联通块点集点权异或和为0-m-1的各有多少个。
对于15%的数据n<=18;
对于另外20%的数据:n<=30;
对于另外25%的数据:n,m<=300;
对于100%的数据,n,m<=1024;
15%做法:
搜索,暴力枚举点集,判断是否是联通块,是就算贡献,这里就不细讲了。
35%做法:
注意到要求是联通块,所以搜索选点的时候加点优化,保证每一步都是联通的即可。
60%做法:
考虑在树上dp,任意选根,设f【i】【j】表示在i的子树中选取联通块,且异或和为j的方案数,g【i】【j】为在i的子树中选取联通块,必定选点i,且异或和为j的方案数。可以发现,对于i的子树考虑,f【i】【j】=g【i】【j】+f【k1】【j】+f【k2】【j】+f【k3】【j】......;其中(k1,k2,k3....为i的所有儿子),那么怎么求g【i】【j】呢,考虑合并答案:
如图,考虑将i的子树的答案一个个合并到i里面,我们按照k1,k2,k3的顺序合并答案,例如合并完k2子树的答案时, g【i】【j】暂时存的值就是选i点,以及可以在k1,k2两棵子树选点的构成的联通块的方案(不考虑在子树k3中的选点情况)。而合并答案的时候,就要在原来的基础上加上在子树k3一定选的方案数:
就这样合并答案即可,时间复杂度:
100%的做法:
我们考虑点分治,将每次要操作的重心为根造出dfs序,然后求强制过根的联通块方案数,然后分成子树分治,显然,算完强制过根的方案后,再算子树内的方案,子树内的方案是必定不过根的,所以可以不重不漏的计算,接下来的难点就在于怎么进行dp。我们可以考虑将联通块的点按dfs序取点,然后对于f【i】【j】表示现在以dfs序为i的点为决策点,异或和为j的方案数。dfs序为i的点一共只有两种情况,选和不选,由于强制过根,如果不选的话,它的子树也不能选,也就是决策点可以转移到f【i+size【sequence【i】】】【j】上(其中sequence【i】为dfs序为i的节点的编号,而size存的是对应编号的节点的子树的大小):
这样,我们发现,每个点只有两种决策,也就是说每个点只会转移两个方向,转移过程的时间复杂度是的,而点分治只需要递归logn层,所以总的时间复杂度:
推荐番:《干物妹!小埋》
上代码(^-^)
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int pan[3005],to[3005],pp[3005],nex[3005],ans[3005],siz[3005],line[3005],v[3005],visit[3005];
int dp[1050][1050],ma,mi,p,mod,n,m,x,y,t,wei;
void dfs(int k,int sum)
{
pan[k]=1;siz[k]=1;
int pu=pp[k],ma=0;
while (pu>0)
{
if (pan[to[pu]]==0 && visit[to[pu]]!=1)
{
dfs(to[pu],sum);siz[k]+=siz[to[pu]];
ma=max(ma,siz[to[pu]]);
}
pu=nex[pu];
}
ma=max(ma,sum-siz[k]);
if (ma<mi){mi=ma;wei=k;}
}
void dfs2(int k)
{
pan[k]=0;siz[k]=1;p++;line[p]=k;
int pu=pp[k];
while (pu>0)
{
if (pan[to[pu]]==1 && visit[to[pu]]!=1)
{
dfs2(to[pu]);siz[k]+=siz[to[pu]];
}
pu=nex[pu];
}
}
void zhao(int k,int large)
{
mod=1000000007;
mi=1000000007;p=0;
dfs(k,large);
dfs2(wei);
for (int i=2;i<=large+1;i++)
{
for (int j=0;j<m;j++)
{
dp[i][j]=0;
}
}
dp[2][v[wei]]=1;
for (int i=2;i<=large;i++)
{
for (int j=0;j<m;j++)
{
dp[i+1][j^v[line[i]]]=(dp[i+1][j^v[line[i]]]+dp[i][j])%mod;
dp[i+siz[line[i]]][j]=(dp[i+siz[line[i]]][j]+dp[i][j])%mod;
}
}
for (int i=0;i<m;i++)
{
ans[i]=(ans[i]+dp[large+1][i])%mod;
}
int ww=2,dang=0,yao[1024],din[1024];
visit[wei]=1;
while (ww<=large)
{
dang++;yao[dang]=line[ww];din[dang]=siz[line[ww]];
ww+=siz[line[ww]];
}
for (int i=1;i<=dang;i++)zhao(yao[i],din[i]);
}
int main()
{
cin>>t;
for (int o=1;o<=t;o++)
{
for (int i=1;i<=n;i++)visit[i]=0;
cin>>n>>m;p=0;
for (int i=0;i<m;i++)ans[i]=0;
for (int i=1;i<=n;i++)pp[i]=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
for (int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
p++;to[p]=y;nex[p]=pp[x];pp[x]=p;
p++;to[p]=x;nex[p]=pp[y];pp[y]=p;
}
zhao(1,n);
for (int i=0;i<m-1;i++)
{
printf("%d ",ans[i]);
}
printf("%d",ans[m-1]);
printf("\n");
}
}