数据范围5000
(f[i])表示至少选(i)对祖孙关系的方案数,然后容斥一下就好了
(g[x][i])表示在(x)的子树选(i)对的方案数,如果不选自己就是一个背包
选自己的方案数为子树内可以与自己匹配的点的数量
最后(f[i]=g[x][i]*fac[n/2-i])
然后怎么容斥都不对……
(f[j])会在(ans[i])种算(C_j^i)次(像这种与阶乘有关的)
[ans[i]=sum_{j=i}^{n/2}C_j^if[j]
]
二项式反演
[f[i]=sum_{j=i}^n(-1)^{j-i}C_j^ig[j]
]
可以用斯特林数优化,但其实直接暴力即可
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f==1?x:-x;
}
#define ll long long
const int N=5004,mod=998244353;
int n,s0[N],s1[N],tmp[N],g[N][N],c[N][N];
vector<int>e[N];
char ch[N];
void dfs(int x,int fa){
g[x][0]=1;
for(auto v:e[x]){
if(v==fa)continue;
dfs(v,x);
for(int i=min(s0[x],s1[x]);i>=0;i--)
for(int j=min(n/2-i,min(s0[v],s1[v]));j>=0;j--)
tmp[i+j]=((ll)g[v][j]*g[x][i]+tmp[i+j])%mod;
for(int i=0;i<=n/2;i++){g[x][i]=tmp[i];tmp[i]=0;}
s0[x]+=s0[v];s1[x]+=s1[v];
}
if(ch[x]=='1'){
s1[x]++;
for(int i=s0[x];i;i--)
g[x][i]=((ll)g[x][i-1]*(s0[x]-i+1)+g[x][i])%mod;
}
else{
s0[x]++;
for(int i=s1[x];i;i--)
g[x][i]=((ll)g[x][i-1]*(s1[x]-i+1)+g[x][i])%mod;
}
}
int main(){
n=read();
scanf("%s",ch+1);
for(int i=1,u,v;i<n;i++){
u=read();v=read();
e[u].push_back(v);e[v].push_back(u);
}
for(int i=0;i<=n/2;i++){
c[i][0]=1;
for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
dfs(1,0);
for(int i=n/2-2,j=2,fac=1;i>=0;i--,j++){
fac=(ll)fac*j%mod;
g[1][i]=(ll)g[1][i]*fac%mod;
}
for(int i=0,ans;i<=n/2;i++){
ans=0;
for(int j=i;j<=n/2;j++)
ans=((ll)((j-i&1)?mod-1:1)*c[j][i]%mod*g[1][j]+ans)%mod;
cout<<ans<<"
";
}
return (0-0);
}
附:(n^2)背包(P2014选课)
- 把自己传给儿子
//设dp[i][j]表示选择以i为根的子树中j个节点。
//u代表当前根节点,tot代表其选择的节点的总额。
void dfs(int u,int tot)
{
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
for(int k=0;k<tot;k++)//这里k从o开始到tot-1,因为v的子树可以选择的节点是u的子树的节点数减一
dp[v][k]=dp[u][k]+val[u];
dfs(v,tot-1)
for(int k=1;k<=tot;k++)
dp[u][k]=max(dp[u][k],dp[v][k-1]);//这里是把子树的值赋给了根节点,因为u选择k个点v只能选择k-1个点。
}
}
- 转后序遍历
f[i][j]=max(f[i-1][j-w[i]]+v[i],f[i-sz[i]][j]);//不选自己则跳过子树,选自己则继承上一个的位置DP更新