题解:P12002 吃猫粮的玉桂狗
好题。
题目的意思就是一些东西有个数限制,要把东西丢到树上,然后有几个东西不能按照某种顺序摆。
考虑容斥。
我们先放弃这个个数限制,先算出这种情况下的答案数。
显然,每个点只要记录下这个点是某个颜色的方案数,转移只要把符合条件的儿子乘起来就好了。这一部分是
代码如下。
void dfs1(int x,int fa){
if(g[x].size()==1&&fa){
for(int i=1;i<=m;i++)dp[x][i][0]=1;
}
for(int i:g[x]){
if(i==fa)continue;
dfs1(i,x);
for(int j=1;j<=m;j++){
ll tmp=0;
for(int k=1;k<=m;k++){
if(!s[j][k]){
if(dp[x][j][0]==0&&dp[i][k][0]!=0)dp[x][j][0]=1;
tmp+=dp[i][k][0];
tmp%=mod;
}
}
dp[x][j][0]*=tmp;
dp[x][j][0]%=mod;
}
}
}
然后考虑某个东西个数不够的情况,显然这个东西我们不能直接容斥,不然就爆了,但是题目有个条件还没用到,就是
所以我们可以枚举下每个超出的物品
朴素的 dp 是
void dfs2(int x,int fa){
for(int i=1;i<=m;i++)dp[x][i][0]=1;
for(int i:g[x]){
if(i==fa)continue;
dfs2(i,x);
ll f[N][N]={0};
for(int j=1;j<=m;j++){
for(int k=0;k<=n;k++){//当前节点的颜色和标记数量
int tmp=0;
for(int l2=0;l2<=k;l2++){
for(int l=1;l<=m;l++){
if(s[j][l])continue;
f[j][k]+=dp[x][j][k-l2]*dp[i][l][l2];
}
}
}
}
for(int j=1;j<=m;j++)for(int k=0;k<=n;k++)dp[x][j][k]=f[j][k];
}
for(int j=n;j>=1;j--)dp[x][mark][j]=dp[x][mark][j-1];
dp[x][mark][0]=0;
}
考虑如何优化这玩意,其实就是因为枚举每个点使用被标记的东西的个数,导致了统计多次儿子对某个东西的贡献,所以这玩意先与处理下直接用即可,复杂度
代码如下。
void dfs2(int x,int fa){
for(int i=1;i<=m;i++)dp[x][i][0]=1;
for(int i:g[x]){
if(i==fa)continue;
dfs2(i,x);
ll f[N][N]={0};
for(int j=1;j<=m;j++){
for(int k=0;k<=n;k++){//当前节点的颜色和标记数量
int tmp=0;
for(int l2=0;l2<=k;l2++){
f[j][k]+=dp[x][j][k-l2]*summ[i][j][l2]%mod;
f[j][k]%=mod;
}
}
}
for(int j=1;j<=m;j++)for(int k=0;k<=n;k++)dp[x][j][k]=f[j][k];
}
for(int j=n;j>=1;j--)dp[x][mark][j]=dp[x][mark][j-1];
dp[x][mark][0]=0;
for(int j=1;j<=m;j++){
for(int k=0;k<=n;k++){
for(int l=1;l<=m;l++){
if(s[j][l])continue;
summ[x][j][k]+=dp[x][l][k];
}
}
}
}