• 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
    ",f[z]*getP(rst,rst)%mod);
        return 0;
    }
  • 相关阅读:
    .NET:在ASP.NET中如何进行IP限制
    vim配置文件和插件
    初学Perl的感受之数据类型
    ASP.NET伪静态详解及配置
    Wayback Machine
    对单元测试的一点感悟——这是一把双刃剑
    python中使用postgres
    第三章 匿名方法
    在C#程序中使用ocx的方法
    可扩展的 “密码强度” 代码示例
  • 原文地址:https://www.cnblogs.com/AKCqhzdy/p/9753580.html
Copyright © 2020-2023  润新知