[BZOJ]1005 明明的煩惱(HNOI2008)
BZOJ的第一頁果然還是很多裸題啊,小C陸續劃水屯些板子。
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.
Solution
碰見這種沒有知識儲備腦子裏都沒有想法的題,考場上還是保佑自己碰到一些自己學過的算法吧。
講這道題之前先來說說prufer編碼是什麽:
①prufer編碼是樹的一種表示形式,不同的編碼與不同的樹形態一一對應;
(不同的樹形態指的是兩棵樹中至少有一條邊連接的點不同)
②根據定理證明,n個點最多能構成(n-2)^n種不同的樹形態。
(至於為什麽是這個式子看看接下來prufer編碼是如何構造的就知道了)
③構造方法:
如圖所示,為一棵有6個結點的樹,每次選出葉子節點中編號最小的一個,將與其相連的那個節點的標號加入數列,再將該葉子結點刪去。直到樹中剩下兩個節點為止。
所以上圖的樹的prufer編碼就是:5 3 1 5(依次刪去2 4 3 1)。
顯而易見,一棵節點數為n的樹的prufer編碼長度為n-2。
由於prufer編碼的每一位都有可能是1~n,不同的prufer編碼有(n-2)^n種。
所以根據第①條一一對應的性質,不同的樹有(n-2)^n種。
利用prufer編碼,我們可以輕易地解決這道題。
從prufer編碼中,我們可以看出一棵樹中所有點的度數,每個點的度數為它在prufer編碼中出現的次數+1。
因此對於題目中規定度數的點,我們可以首先確定它們在prufer編碼中的位置。
假設規定度數的點有p個,度數分別為a1、a2……ap。
那麽把這p個點填進prufer編碼的方案數是。(排列組合、乘法原理瞎推)
那麽prufer編碼中剩下的空位有個,未規定度數的節點有n-p個,所以方案數再乘上即可。
由於答案沒有取模,所以要用到高精度乘/除單精度。
題目中所說的無解情況有3種:
①;
②;
③。
時間復雜度寫得不是太糟都能過,註意高精度數的位數。
#include <cstdio> #include <algorithm> #include <cstring> #define ll long long #define mod 1000000000 #define MS 354 using namespace std; struct hp { int len; ll ar[MS]; friend hp operator/(const hp& a,int b) { register ll lt=0; register int i; hp c; memset(&c,0,sizeof(c)); c.len=a.len; for (i=a.len-1;i>=0;--i) { lt=lt*mod+a.ar[i]; c.ar[i]=lt/b; lt%=b; } if (!c.ar[c.len-1]) --c.len; return c; } friend hp operator*(const hp& a,int b) { register int i,j; hp c; memset(&c,0,sizeof(c)); c.len=a.len; for (i=0;i<a.len;++i) { c.ar[i]+=a.ar[i]*b; c.ar[i+1]+=c.ar[i]/mod; c.ar[i]%=mod; } if (c.ar[c.len]) ++c.len; return c; } }sum; int n,rn,uk; inline int read() { int n=0,f=1; char c=getchar(); while (c<‘0‘ || c>‘9‘) {if(c==‘-‘)f=-1; c=getchar();} while (c>=‘0‘ && c<=‘9‘) {n=n*10+c-‘0‘; c=getchar();} return n*f; } int main() { register int i,x; sum.ar[0]=1; sum.len=1; n=read(); rn=n-2; for (i=1;i<=rn;++i) sum=sum*i; for (i=1;i<=n;++i) { x=read()-1; if (x>0) {if (rn>=x) {for (rn-=x;x>=2;--x) sum=sum/x;} else return 0*printf("0");} else if (x==-2) ++uk; else return 0*printf("0"); } if (rn&&!uk) return 0*printf("0"); for (i=1;i<=rn;++i) sum=sum/i*uk; printf("%lld",sum.ar[sum.len-1]); for (i=sum.len-2;i>=0;--i) printf("%09lld",sum.ar[i]); }
Last Word
小C看到這道題的時候就覺得這肯定不是正常題,就是沒有看過相關的東西死都做不出來的那種。
結果真的是這樣。
[BZOJ]1005 明明的煩惱(HNOI2008)