—————————————————————————————————
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