问题:有一课含有n(n<=2e5)个结点的数,有m(m<=1000)个结点是红色的,其余的结点是黑色的。现从树中选若干数量的结点,其中红色的恰有k个,并且每个结点都不是其他任何另一个结点的后代,分别求出k=0,1,2,...,m的选法种数。(树根为1)
又是一道树形背包问题。只不过这个问题和普通的树形背包正好相反,选了一个结点就不能选它的祖先,但解法都是差不多的。
设dp[u][i]为在第u个结点及其子树下,红色结点数恰为i个的选法总数,则dp[u]为它的所有子结点的dp值构成的多项式的乘积加上结点本身的贡献,状态转移的时候模拟多项式的乘法就行了。
用FFT似乎也可以,但我没试,因为复杂度并不会比用siz上限优化了的直接转移要低,而且取模比较麻烦。
另外注意这道题的dp数组用long long会爆内存,只能用int存储,在运算的中间过程转化成long long。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=2e5+10,inf=0x3f3f3f3f,mod=1e9+7; 5 int n,m,hd[N],ne,a[N],siz[N],dp[N][1000+10],b[1000+10]; 6 struct E {int v,nxt;} e[N]; 7 void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;} 8 void dfs(int u) { 9 memset(dp[u],0,sizeof dp[u]); 10 siz[u]=0,dp[u][0]=1; 11 for(int i=hd[u]; ~i; i=e[i].nxt) { 12 int v=e[i].v; 13 dfs(v); 14 for(int j=0; j<=siz[u]+siz[v]; ++j)b[j]=0; 15 for(int j=siz[u]; j>=0; --j)if(dp[u][j]) 16 for(int k=siz[v]; k>=0; --k)if(dp[v][k]) 17 b[j+k]=(b[j+k]+(ll)dp[u][j]*dp[v][k])%mod; 18 for(int j=0; j<=siz[u]+siz[v]; ++j)dp[u][j]=b[j]; 19 siz[u]+=siz[v]; 20 } 21 siz[u]+=a[u],dp[u][a[u]]=(dp[u][a[u]]+1)%mod; 22 } 23 24 int main() { 25 memset(hd,-1,sizeof hd),ne=0; 26 scanf("%d%d",&n,&m); 27 for(int i=2; i<=n; ++i) { 28 int u; 29 scanf("%d",&u); 30 addedge(u,i); 31 } 32 for(int i=0; i<m; ++i) { 33 int u; 34 scanf("%d",&u); 35 a[u]=1; 36 } 37 dfs(1); 38 for(int i=0; i<=m; ++i)printf("%d ",dp[1][i]); 39 return 0; 40 }