CF917D 题解

CF917D

比较妙。考场上推出了 \(O(n^3)\) 的做法然后发现不会扩展Cayley定理就打摆子了。

我算是tm意识到我有多菜了。

前置芝士:扩展Cayley定理

如果您仍然不知道扩展Cayley定理是什么东西的话那么请看这个的结尾。

首先我们看见这个“恰好”感觉很难受,套路二项式反演一下。

\(f_k\) 为钦定 \(k\) 条边和原来一样,其他放任自流的方案数。设 \(g_k\) 为恰好有 \(k\) 条边和原来一样的方案数。那么显然有

\[f_k=\sum_{i=k}^{n-1}\binom{i}{k}g_i \]

根据二项式反演:

\[g_k=\sum_{i=k}^{n-1}(-1)^{i-k}\binom{i}{k}f_i \]

这个可以 \(O(n^2)\) 暴力扫。所以我们现在的问题是怎么求出 \(f_i\)

我们发现,钦定 \(k\) 条边之后一定会剩下 \(n-k\) 个连通块,但是它们的大小是不知道的。所以我们可以来一波树形dp。

\(dp[u][i][j]\) 为以 \(u\) 为根的子树中,分了 \(i\) 个连通块, \(u\) 所在的连通块大小为 \(j\) 的方案数。那么我们枚举 \(u\) 和儿子 \(v\) 之间连的边是否被选。(下面的 \(f\) 是个更新后的 \(dp\) 数组)

被选择:显然有 \(f[u][i+j-1][k+l]=dp[u][i][k]\times dp[v][j][l]\) 。具体什么意思,考虑两部分连通块数量相加,再减掉连上的边连接的连通块。

不被选择:有 \(f[u][i+j][k]=l\times dp[u][i][k]\times dp[v][j][l]\) 。两部分的连通块数量相加,第三位由于没有合并显然不变。

这个东西就是 \(O(n^3)\) 的了(然后我考场上忘记Cayley定理是什么东西了挂灵了,还是想念叨一下)。

考虑优化这个东西。某个 \(2\sqrt 2\)WC2019 数树里有一个小trick。

考虑到扩展Cayley定理里的式子 \(\prod_{i=1}^ka_i\) 的意义,是每个连通块里面选一个标记点连上。所以我们不如直接考虑 \(dp[u][i][0/1]\)\(u\)\(i\) 个连通块,该连通块内被标记的点是否被选的方案数(被标记的点是什么不重要)。所以我们现在又有四个 \(dp\) 式子:

  1. 选标记点,不连边: \(f[u][i+j][1]=dp[u][i][1]\times dp[v][j][1]\) 。如果不连边那么 \(v\) 连通块内的标记点显然必须被选(因为如果不选以后也不会再选了)。然后自己连通块内的标记点选择情况不变。
  2. 不选标记点,不连边: \(f[u][i+j][0]=dp[u][i][0]\times dp[v][j][1]\) 。和上面那个一样。
  3. 选标记点,连边: \(f[u][i+j-1][1]=dp[u][i][0]\times dp[v][j][1]+dp[u][i][1]\times dp[v][j][0]\) 。如果选了标记点那么两个连通块必须有且仅有一个被选。
  4. 不选标记点,连边: \(f[u][i+j-1][0]=dp[u][i][0]\times dp[v][j][0]\) 。两边都不能选。

这样就是 \(O(n^2)\) 的了。使用vector卡空间可以通过 \(n\le 8000\) ,空间限制 \(256\rm{MB}\) 的限制。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#define C(j,i) (1ll*jc[j]*inv[i]%mod*inv[j-i]%mod)
using namespace std;
const int mod=1000000007;
int n,t,head[8010],jc[8010],inv[8010],size[8010];
int f[8010][2];
vector<int>dp[2][8010];
struct node{
    int v,next;
}edge[8010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*ans*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
void dfs(int x,int fa){
    dp[0][x].resize(2);dp[1][x].resize(2);
    dp[0][x][1]=dp[1][x][1]=1;size[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=fa){
            dfs(edge[i].v,x);
            for(int j=1;j<=size[x];j++){
                for(int k=1;k<=size[edge[i].v];k++){
                    f[j+k][0]=(f[j+k][0]+1ll*dp[0][x][j]*dp[1][edge[i].v][k]%mod)%mod;
                    f[j+k][1]=(f[j+k][1]+1ll*dp[1][x][j]*dp[1][edge[i].v][k]%mod)%mod;
                    f[j+k-1][0]=(f[j+k-1][0]+1ll*dp[0][x][j]*dp[0][edge[i].v][k]%mod)%mod;
                    f[j+k-1][1]=(f[j+k-1][1]+1ll*dp[1][x][j]*dp[0][edge[i].v][k]%mod+1ll*dp[0][x][j]*dp[1][edge[i].v][k]%mod)%mod;
                }
            }
            size[x]+=size[edge[i].v];
            dp[0][x].resize(size[x]+1);dp[1][x].resize(size[x]+1);
            for(int j=1;j<=size[x];j++){
                dp[0][x][j]=f[j][0];dp[1][x][j]=f[j][1];
                f[j][0]=f[j][1]=0;
            }
            dp[0][edge[i].v].clear();dp[1][edge[i].v].clear();
            dp[0][edge[i].v].shrink_to_fit();
            dp[1][edge[i].v].shrink_to_fit();
        }
    }
}
int g[8010];
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1,0);
    static int jc[8010],inv[8010],g[8010];jc[0]=inv[0]=1;
    for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
    inv[n]=qpow(jc[n],mod-2);
    for(int i=n-1;i>=1;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;
    for(int i=0;i<=n-2;i++){
        g[i]=1ll*dp[1][1][n-i]*qpow(n,n-i-2)%mod;
    }
    g[n-1]=1;
    for(int i=0;i<=n-1;i++){
        int ret=0;
        for(int j=i;j<=n-1;j++){
            ret=(ret+1ll*(((j-i)&1)?mod-1:1)*C(j,i)%mod*g[j]%mod)%mod;
        }
        printf("%d ",ret);
    }
    return 0;
}
posted @ 2022-10-10 17:21  gtm1514  阅读(13)  评论(0编辑  收藏  举报