[BZOJ 1005] 明明的煩惱 && [BZOJ 1211] 樹的計數【組合數學】


—————————————————————————————————
HNOI 2008 明明的煩惱

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
—————————————————————————————————
一道比較簡單的組合數學題。

首先我們要了解一下Prufer Sequence(普呂弗序列)(內容摘自zyj PPT).

Prufer Sequence是一個和某棵結點數爲n的樹唯一對應的一個長度爲n-2的整數序列。定義如下:

假定已知的n個頂點標誌爲1,2,…,n,假定T是其中的一棵樹。設a1爲葉節點中有標號最小者,與a1連接的點爲b1,從T中消去點a1和邊(a1,b1),再從餘下的數T1中尋找標號最小的葉節點,設爲a2,a2的鄰接點爲b2,從從T1中消去點a2和邊(a2,b2)。如此步驟n-2次,直到最後剩下一條邊爲止。於是一棵樹T對應一序列:b1,b2,…,bn-2
這些數是1~n中的數,並且允許重複。

相反地,用b1,b2…bn-2可以恢復樹T本身,因爲消去的是樹葉中標號最小的,而且它和b1是鄰接的。
即給出一序列b1,b2,…,bn-2,其中1<=bi<=n,i=1,2,…,n-2.可恢復與之對應的樹,方法如下:
有兩個序列,一個是(1)1,2,….,n,另一個是(2)b1,b2,…,bn-2
在序列(1)中找出第一個不出現在序列(2)b1,b2,…,bn-2中的數,這個數顯然便是a1,同時形成邊(a1,b1),並從(1)中消去a1,從(2)中消去b1。在餘下的(1)和(2)中繼續以上的步驟n-2次,直到序列(2)爲空集爲止。這時序列(1)中剩下的兩個數x,y,邊(x,y)就是樹T的最後一條邊。

看不懂? 來看個栗子

樹T

這裏寫圖片描述

找編號最小的節點,爲2,與他相連的爲3,將3加入序列

這裏寫圖片描述

消掉點2和邊(2,3),找到當前編號最小的節點,爲3,與他相連的爲1,將1加入序列

這裏寫圖片描述

這裏寫圖片描述

重複這個操作

這裏寫圖片描述

這裏寫圖片描述

… 直到最後得到這個序列

這裏寫圖片描述

同樣,我們用這個序列恢復這棵樹

現有(1,7)之前並未消去
這裏寫圖片描述

開始操作
這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述

這樣很容易看出,每一個Prufer Sequence對應一顆唯一的樹(一一對應)。

—————————————————————————————————

回到題目中來。

我們發現,樹中結點的度和結點號在此序列中出現的次數有關(結點i在序列中出現的次數=degree[i]-1).

由題意,有題目給出度數的限制,同時也就限制了序列的一些條件。樹的所有可能情況也就是序列可能的情況。

設度數沒有限制的點的數量爲num,有限制的點在序列中出現的總次數爲sum

可知,n-num個沒有限制的點在序列中的排列總情況爲

這裏寫圖片描述

(從n-2個位置選sum個位置填充這些數*排列方式爲多重集的全排列方案數)

剩下n-sum-2 個位置可由num個數填充,方案數爲

這裏寫圖片描述

所以答案爲
這裏寫圖片描述

因爲可能爆long long,用分解質因數求以下就可以了。

然後答案也比較畸形,要用高精度,好像要開3000位,不然就神作了。

注意當任意degree=0時是無解的。

另外,當sum>n-2同樣也是無解的。

注意特判n=1的情況,if(degree[1]=0) ans=1, else ans=0;

然後就可以愉快的AC辣–

附一段代碼

#include<cstdio>
#include<cctype>

inline int readint(){
    int x=0; char c=getchar();  bool minu=false;
    while(!isdigit(c) && c!= '-') c=getchar(); 
    if(c=='-') minu=true; else x=c-'0';
    while(isdigit(c=getchar())) x=(x*10)+c-'0';
    return minu?-x:x;
}

const int maxn=1000+10;
int degree[maxn];

int form[maxn],prime[maxn],facnum[maxn],cnt;
void MakePrimeList(int n){                          
    for(int i=2;i<=n;i++) if(!form[i]){
        prime[cnt++]=i;
        for(int j=i*i;j<=n;j+=i) form[j]=true;
    }
}

long long pow(long long a,int n){                       //遞推快速冪
    long long ans=1;
    while(n){
        if(n&1) ans*=a;
        a*=a; n>>=1;
    }
    return ans;
}
void fac(int x,int t){                                  //分解質因數
    if(!x || !t) return;
    for(int i=0;i<cnt;i++)
        while(!(x%prime[i])) facnum[i]+=t,x/=prime[i];
}

long long ans[3000]={1},digit=1;
void count(){
    for(int i=0;i<cnt;i++) if(facnum[i]){
        int x=0;
        for(int k=0;k<facnum[i];k++){
            for(int j=0;j<digit;j++) ans[j]*=prime[i];
            for(int j=0;j<digit;j++){
                ans[j]+=x;
                x=ans[j]/10;
                ans[j]%=10;
            }
        while(x) ans[digit++]=x%10,x/=10;
        }
    }
}

int main(){
    int n=readint(),sum=0,num=0;
    MakePrimeList(maxn);
    if(n==1){
        if(readint()) putchar('0');  else putchar('1');
        return 0;
    }
    for(int i=0;i<n;i++){
        degree[i]=readint();
        if(!degree[i]) { printf("0"); return 0; }
        else if(degree[i]==-1) num++;
        else sum+=degree[i]-1;
    }
    if(sum>n-2) { printf("0"); return 0; }
    fac(num,n-sum-2);
    for(int i=n-sum-1;i<=n-2;i++) fac(i,1);
    for(int i=0;i<n;i++) if(degree[i]!=1)
        for(int j=2;j<degree[i];j++) fac(j,-1);
    count();
    for(int i=digit-1;i>=0;i--) putchar(ans[i]+'0');
    return 0;
}

然後1211就是簡化了的題目了(建議先完成)

–By Foggy
2015.6.22

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章