zoukankan      html  css  js  c++  java
  • bzoj1211树的计数 x bzoj1005明明的烦恼 题解(Prufer序列)

    1211: [HNOI2004]树的计数

    Time Limit: 10 Sec  Memory Limit: 162 MB
    Submit: 3432  Solved: 1295
    [Submit][Status][Discuss]

    Description

    一个有n个结点的树,设它的结点分别为v1, v2, …, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵。给定n,d1, d2, …, dn,编程需要输出满足d(vi)=di的树的个数。

    Input

    第一行是一个正整数n,表示树有n个结点。第二行有n个数,第i个数表示di,即树的第i个结点的度数。其中1<=n<=150,输入数据保证满足条件的树不超过10^17个。

    Output

    输出满足条件的树有多少棵。

    Sample Input

    4
    2 1 2 1

    Sample Output

    2
     
     
     

    先丟几个Prufer序列的性质:

    1.与无根树一一对应。

    2.度数为$d_i$的点会在Prufer序列中出现$d_i-1$次。

    3.一个$n$个节点的完全图的生成树个数为$n^{n-2}$

      解释一下:prufer序列长为$n-2$,每个位置有$n$种可能性。

    4.对于给定每个点度数的无根树,共有$frac{(n-2)!}{prod limits _{i=1}^n {(d_i-1)!}}$种情况。

      其实就是$d_i-1$个$i$的可重全排列。

    那么这道题就用上面这个结论切掉就好了。求组合数直接分解质因数,记得要特判$n==1$及不联通的情况。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    const int N=155;
    int d[N],n;
    int vis[N],pri[N],res[N],tot,sum,ans[N*5],bu[N];
    void getprime()
    {
        for(int i=2;i<=n;i++)
        {
            if(!vis[i])pri[++tot]=i,res[i]=tot;
            for(int j=1;j<=tot;j++)
            {
                if(pri[j]*i>n)break;
                vis[i*pri[j]]=1;res[i*pri[j]]=j;
                if(i%pri[j]==0)break;
            }
        }
    }
    void divi(int x,int val)
    {
        while(x!=1)bu[res[x]]+=val,x/=pri[res[x]];
    }
    void mult(int a[],int x)
    {
        int k=0;
        for(int i=1;i<=a[0];i++)
        {
            int tmp=a[i]*x+k;
            a[i]=tmp%10;
            k=tmp/10;
        }
        while(k)a[++a[0]]=k%10,k/=10;
    }
    int main()
    {
        scanf("%d",&n);
        if(n==1)
        {
            int deg;
            scanf("%d",&deg);
            if(!deg)puts("1");
            else puts("0");
            return 0;
        }
        getprime();
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&d[i]);
            if(!d[i])
            {
                puts("0");
                return 0;
            }
            sum+=d[i]-1;
        }
        if(sum!=n-2)
        {
            puts("0");
            return 0;
        }
        for(int i=n-2;i>=2;i--)
            divi(i,1);
        for(int i=1;i<=n;i++)
        {
            for(int j=d[i]-1;j>=2;j--)
                divi(j,-1);
        }
        ans[0]=ans[1]=1;
        for(int i=1;i<=tot;i++)
            while(bu[i]--)mult(ans,pri[i]);
        for(int i=ans[0];i;i--)
            printf("%d",ans[i]);
        return 0;
    }
    View Code

    1005: [HNOI2008]明明的烦恼

    Time Limit: 1 Sec  Memory Limit: 162 MB
    Submit: 7125  Solved: 2818
    [Submit][Status][Discuss]

    Description

      自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在
    任意两点间连线,可产生多少棵度数满足要求的树?

    Input

      第一行为N(0 < N < = 1000),
    接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1

    Output

      一个整数,表示不同的满足要求的树的个数,无解输出0

    Sample Input

    3
    1
    -1
    -1

    Sample Output

    2

    HINT

      两棵树分别为1-2-3;1-3-2

    本题的问题在于有的点度数是不确定的。

    所以我们先求出$sum=sum d_i-1$,之后从总长度$n-2$中选出这些

    对于剩下的不确定度数的部分,我们设已知度数点的个数为$cnt$

    那么现在有$n-2-sum$个位置可以任意排列$n-cnt$个点

    易得最终答案为

    $C_{n-2}^{sum}*frac{(n-2)!}{prod limits _{i=1}^{cnt} {(d_i-1)!}}*(n-cnt)^{n-2-sum}$

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int N=1005;
    int d[N],n;
    int vis[N],pri[N],res[N],tot,sum,cnt,ans[N*5],bu[N];
    void getprime()
    {
        for(int i=2;i<=n;i++)
        {
            if(!vis[i])pri[++tot]=i,res[i]=tot;
            for(int j=1;j<=tot;j++)
            {
                if(pri[j]*i>n)break;
                vis[i*pri[j]]=1;res[i*pri[j]]=j;
                if(i%pri[j]==0)break;
            }
        }
    }
    void divi(int x,int val)
    {
        while(x!=1)bu[res[x]]+=val,x/=pri[res[x]];
    }
    void mult(int a[],int x)
    {
        int k=0;
        for(int i=1;i<=a[0];i++)
        {
            int tmp=a[i]*x+k;
            a[i]=tmp%10;
            k=tmp/10;
        }
        while(k)a[++a[0]]=k%10,k/=10;
    }
    
    int main()
    {
        scanf("%d",&n);
        getprime();
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&d[i]);
            if(d[i]!=-1)sum+=d[i]-1,cnt++;
        }
        for(int i=n-2;i>=2;i--)
            divi(i,1);
        for(int i=n-2-sum;i>=2;i--)
            divi(i,-1);
        for(int i=1;i<=n;i++)
        {
            if(d[i]!=-1)
            {
                for(int j=d[i]-1;j>=2;j--)
                    divi(j,-1);
            }
        }
        ans[0]=ans[1]=1;
        for(int i=1;i<=n-2-sum;i++)
            mult(ans,n-cnt);
        for(int i=1;i<=tot;i++)
            while(bu[i])mult(ans,pri[i]),bu[i]--;
        for(int i=ans[0];i>=1;i--)
            printf("%d",ans[i]);
        return 0;
    }
    View Code
  • 相关阅读:
    JavaScript 为字符串添加样式 【每日一段代码80】
    JavaScript replace()方法 【每日一段代码83】
    JavaScript for in 遍历数组 【每日一段代码89】
    JavaScript 创建用于对象的模板【每日一段代码78】
    html5 css3 新元素简单页面布局
    JavaScript Array() 数组 【每日一段代码88】
    JavaScript toUTCString() 方法 【每日一段代码86】
    位运算
    POJ 3259 Wormholes
    POJ 3169 Layout
  • 原文地址:https://www.cnblogs.com/Rorschach-XR/p/11240290.html
Copyright © 2011-2022 走看看