NOI.AC #31. MST

好像又是神仙dp。。。。gan了一早上

首先这是个计数类问题,上DP,

对于一个最小生成树,按照kruskal是一个个联通块,枚举边小到大合成的

假如当前边是树边,那么转移应该还是枚举两个块然后合并

假如不是树边那么就在所有联通块所有非树边中任选一条

两个相邻树边之间的非树边方案应该是P(所有联通块总边数-(当前枚举到那条边-1),r-l-1)

然而按照我现在的智商还是不会捉

%了题解发现一个非常强大的性质,就是对于一个整数的无序拆分很小,40只有37338

设f[zt],其中zt表示一个状态,由一些联通块的大小组成,总和为n

这样可以爆搜一波把所有无序拆分也就是状态弄出来,并给一个新编号

转移就是枚举两个联通块然后合并

若第i,j个合并

(新状态的方案)+=(这个状态的方案)*(两条树边之间其他边选择的方案)*(第i个联通块的大小)*(第j个联通块的大小)

 

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;

int n;
struct zhuangtai
{
    int u[50];
    friend bool operator >(zhuangtai z1,zhuangtai z2)
    {
        if(z1.u[0]==z2.u[0])
        {
            for(int i=1;i<=z1.u[0];i++)
                if(z1.u[i]!=z2.u[i])return z1.u[i]>z2.u[i];
        }
        return z1.u[0]>z2.u[0];
    }
    friend bool operator <(zhuangtai z1,zhuangtai z2)
    {
        if(z1.u[0]==z2.u[0])
        {
            for(int i=1;i<=z1.u[0];i++)
                if(z1.u[i]!=z2.u[i])return z1.u[i]<z2.u[i];
        }
        return z1.u[0]<z2.u[0];
    }
    int getsum()
    {
        int ret=0;
        for(int i=1;i<=u[0];i++)
            ret=(ret+(u[i]*(u[i]-1)/2)%mod)%mod;
        return ret;
    }
}mp[41000];int z,g[50];
bool cmp(zhuangtai z1,zhuangtai z2){return z1>z2;}
map<zhuangtai,int>id;//通过状态找编号 
void dfs(int d,int last)//预处理拆分n的方案 
{
    if(d==n)
    {
        z++;
        for(int i=0;i<=g[0];i++)mp[z].u[i]=g[i];
        return ;
    }
    for(int i=last;i+d<=n;i++) 
    {
        g[++g[0]]=i;
        dfs(i+d,i);
        g[g[0]--]=0;
    }
}

LL quick_pow(LL A,LL p)
{
    LL ret=1;
    while(p!=0)
    {
        if(p%2==1)ret=ret*A%mod;
        A=A*A%mod;p/=2;
    }
    return ret;
}
LL fac[110000],fac_inv[110000];
LL getP(int n,int m){return fac[n]*fac_inv[n-m]%mod;}


zhuangtai t;int h[50];
int getnzt(int zt,int x,int y)
{
    memcpy(h,mp[zt].u,sizeof(h));
    int d=h[x]+h[y]; memset(t.u,0,sizeof(t.u));
    for(int i=1;i<=h[0];i++)
    {
        if(i!=x&&i!=y)
        {
            if(d!=-1&&h[i]>d)t.u[++t.u[0]]=d,d=-1;
            t.u[++t.u[0]]=h[i];
        }
    }
    if(d!=-1)t.u[++t.u[0]]=d;
    return id[t];
}
int a[50];LL f[41000];
int main()
{
    fac[0]=1,fac_inv[0]=1;
    for(int i=1;i<=100000;i++)
        fac[i]=fac[i-1]*i%mod,fac_inv[i]=quick_pow(fac[i],mod-2);
    
    scanf("%d",&n);
    for(int i=1;i<n;i++)scanf("%d",&a[i]);
    z=0;dfs(0,1);
    sort(mp+1,mp+z+1,cmp);
    for(int i=1;i<=z;i++)id[mp[i]]=i;
    
    memset(f,0,sizeof(f));f[1]=1;
    for(int zt=1;zt<=z;zt++)
        if(f[zt]>0)
        {
            int e=n-mp[zt].u[0]+1;//轮到第几条边用来合并
            LL P=getP(mp[zt].getsum()-(a[e-1]),a[e]-a[e-1]-1);//两条树边中间其他边选择的方案数 
            
            for(int i=1;i<=mp[zt].u[0];i++)
                for(int j=i+1;j<=mp[zt].u[0];j++)
                {
                    int nzt=getnzt(zt,i,j);
                    f[nzt]=(f[nzt]+f[zt]*P%mod*mp[zt].u[i]%mod*mp[zt].u[j]%mod)%mod;
                }
        }
    int rst=n*(n-1)/2-a[n-1];
    printf("%lld\n",f[z]*getP(rst,rst)%mod);
    return 0;
}

 

posted @ 2018-10-08 11:57  AKCqhzdy  阅读(211)  评论(0编辑  收藏  举报