[codeforces 917D]Stranger Trees

题目大意

给定一棵n个节点的树。对于k=0到n-1,输出有多少棵n个节点的带标号无根树恰好与给定的树有k条公共边。答案模 109+7
n≤100

分析

我们从prufer序的角度考虑这道题。
选择了若干条原树的边后,我们得到一些联通块。把这些联通块都缩在一起,假设剩下m个点,接下来我们要去连接这些联通块,得到一棵树,然后考虑这棵树的prufer序是怎样的。
由于当前叶子的父亲所表示的联通块G中任意一个点都可能与叶子联通块相连,所以有 |G| 种可能。那么prufer序的m-2位共有 (mi=1|Gi|)m2=nm2 种可能情况。
但是prufer序不能完全表示这棵树,因为由prufer序只能确定父亲联通块中哪个点连了出去,不知道连到哪个点,假设叶子联通块是G’,那么还有 |G| 种可能情况。再算上连接最后两个联通块的边的可能情况,所以上式还要乘上 mi=1|Gi| 才能正确算出剩下m个联通块的答案。
知道上面的东西就可以dp了。设f[i][j][k]表示原树中以i为根的子树,分成了j个联通块(可以由此知道选了多少条原树的边),其中i所在联通块大小为k时的答案。转移时枚举儿子,然后枚举儿子的后两维。转移看起来有五重循环,但是可以通过树形背包的分析方法发现时间复杂度其实是 O(n4)
这样就做完了吗?没有!
我们发现这样算出来的是与原树至少有k条公共边的答案,需要一个 O(n2) 的容斥算出正确答案。这个就不具体写了。

#include 
#include 
#include 

using namespace std;

const int N=105,mo=1e9+7;

typedef long long LL;

int n,f[N][N][N],Size[N],g[N][N],ans,C[N][N],F[N],tot,h[N],e[N<<1],nxt[N<<1];

void Add(int x,int y)
{
    e[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}

void dp(int x,int y)
{
    Size[x]=1;
    f[x][1][1]=1;
    for (int i=h[x];i;i=nxt[i]) if (e[i]!=y)
    {
        dp(e[i],x);
        memset(g,0,sizeof(g));
        for (int j=1;j<=Size[x];j++)
        {
            for (int p=1;p<=Size[x]-j+1;p++) if (f[x][j][p])
            {
                for (int k=1;k<=Size[e[i]];k++)
                {
                    for (int q=1;q<=Size[e[i]]-k+1;q++) if (f[e[i]][k][q])
                    {
                        g[j+k][p]=(g[j+k][p]+(LL)f[x][j][p]*f[e[i]][k][q]%mo*q)%mo;
                        g[j+k-1][p+q]=(g[j+k-1][p+q]+(LL)f[x][j][p]*f[e[i]][k][q])%mo;
                    }
                }
            }
        }
        Size[x]+=Size[e[i]];
        memcpy(f[x],g,sizeof(g));
    }
}

int main()
{
    scanf("%d",&n);
    for (int i=1,x,y;iscanf("%d%d",&x,&y);
        Add(x,y); Add(y,x);
    }
    dp(1,0);
    C[0][0]=1;
    for (int i=1;i<=n;i++)
    {
        C[i][0]=1;
        for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
    }
    F[n-1]=f[1][1][n];
    for (int i=2;i<=n;i++)
    {
        F[n-i]=0;
        for (int j=1;j<=n-i+1;j++) F[n-i]=(F[n-i]+(LL)f[1][i][j]*j)%mo;
        for (int j=0;j2;j++) F[n-i]=(LL)F[n-i]*n%mo;
    }
    for (int i=0;ifor (int j=i+1,sig=-1;jif (F[i]<0) F[i]+=mo;
        printf("%d ",F[i]);
    }
    return 0;
}

你可能感兴趣的:(DP,容斥原理)