HNOI2008 明明的煩惱 (purfer序列 + 組合數學)
傳送門
這道題題意描述很清楚,不過我自己做的時候確實是一頭霧水……又看了題解,發現要用到一個新知識,叫purfer序列。
我們來簡單說一下什麽是purfer序列。它可以被看作一種樹的表現形式。一棵含有n個節點的樹可以用一個長度為n-2的purfer序列表示,而其中每一個樹都是1~n之間的一個數。
每一棵樹,它都有自己唯一的purfer序列,反過來,對於每一個purfer序列,都能獲得唯一的一棵樹。也就是說,樹與其purfer序列一一對應。
先說一下怎麽求purfer序列。首先找出這棵樹中,節點編號數最小的一個葉子結點(度數為1,只有連向其父親的一條邊),把與它相連的那個節點加入到purfer序列中,並且將這個節點和與之相連的邊從這棵樹中刪除。重復上述過程n-2步,得到一個長度為n-2的purfer序列和一個只有兩個節點的樹。
(還是偷一下大神的圖來描述這件事)
看看下面的例子:
假設有一顆樹有 5 個節點,四條邊依次為:(1, 2), (1, 3), (2, 4), (2, 5),如下圖所示:
第 1 步,選取具有最小標號的葉子節點 3,將與它相連的點 1 作為第 1 個 Purfer Number,並從樹中刪掉節點 3:
第 2 步,選取最小標號的葉子節點 1,將與其相連的點 2 作為第 2 個 Purfer Number,並從樹中刪掉點 1:
第 3 步,選取最小標號的葉子節點 4,將與其相連的點 2 作為第 3 個 Purfer Number,並從樹中刪掉點 4:
最後,我們得到的 Purfer Sequence 為:1 2 2
既然如此,purfer序列就求好了,那我們再說一下怎麽通過purfer序列求它相對應的樹。
先把所有節點的度數賦為1,再加上其在Purfer序列中出現過的次數,得到每一個節點的度。每次選取編號最小的度數為1的節點(比如當前枚舉到第i個點),將這個節點和Purfer序列中第i個數所對應的點連一條邊,並且把這兩個點的度數-1。最後得到兩個度數為1的點,我們再把他們連邊,加入到樹中。這樣就成功的通過purfer序列求出一棵樹了。
(我們再偷一次大神的圖並且以上面的例子為例描述一下這個過程)(感謝大神orz)
頂點 | 1 | 2 | 3 | 4 | 5 |
度 | 2 | 3 | 1 | 1 | 1 |
第 1 次執行,選取最小標號度為 1 的點 3 和 Purfer Sequence 中的第 1 個數 1 連邊:
將 1 和 3 的度分別減一:
頂點 | 1 | 2 | 3 | 4 | 5 |
度 | 1 | 3 | 0 | 1 | 1 |
第 2 次執行,選取最小標號度為 1 的點 1 和 Purfer Sequence 中的第 2 個數 2 連邊:
將 1 和 2 的度分別減一:
頂點 | 1 | 2 | 3 | 4 | 5 |
度 | 0 | 2 | 0 | 1 | 1 |
第 3 次執行,將最小標號度為 1 的點 4 和 Purfer Sequence 第 3 個數 2 連邊:
將 2 和 4 的度分別減一:
頂點 | 1 | 2 | 3 | 4 | 5 |
度 | 0 | 1 | 0 | 0 | 1 |
最後,還剩下兩個點 2 和 5 的度為 1,連邊:
這樣我們就知道,purfer序列必然與樹是一一對應的。
而且我們還知道了一條性質,一個點在purfer序列中出現的次數等於其度數-1.
那我們來看一下這道題。
首先考慮無解的情況,這個很好判斷,如果任意一個點的度數是0或者大於n-1那麽就無解,否則有解。
之後再看一般的情況。我們假設一共有m個節點是有度數限制的,剩下n-m個節點沒有度數限制。那麽sum = sigma 1~m(d[i] - 1)
也就是說,這些點占據了purfer序列中sum個位置(一共有n-2個位置),所以這次選擇的種類是C(n-2,sum)
之後對於這個長度為sum的序列,我們考慮一下,第一個位置可以填入d[1]-1個數,選擇的方案為C(sum,d[1]-1),那麽在第二個位置可以填d[2]-1個數,選擇的方案就是C(sum-(d[1]-1),d[2]-1)。
這樣推下去,可以得到總的排列數是:(偷一下圖吧(^_^))
之後,因為剩下的n-2-sum個位置,每個沒有度數限制,所以可以隨便填,那麽就還有m^(n-2-sum)種情況。把兩者相乘即為答案。
不過寫高精度是不可能的。
我們可以對每個元素都進行質因數分解,開個桶記錄一下每個數的出現次數,最後把他們乘起來就好。可以先行約去分子分母中相同的數。
不過最後一波把所有數乘起來還是要高精的……不過反正是高精乘低精,比較好寫。
寫題的時候還有個小插曲……質因數分解的時候,非常智障的寫成了while(p),結果導致出現了floating point exception :8這麽個錯誤(好像在Windows下是RE)
不管怎麽說,以後要註意啊。
上一下代碼。(這題其實很神奇因為不需要求puffer序列)
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<queue> #include<set> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar(‘\n‘) using namespace std; const int M = 10005; int n,len,a,sum,k,tot; int t[10001],ans[M+1],prime[M+1]; bool np[M]; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < ‘0‘ || ch > ‘9‘) { if(ch == ‘-‘) op = -1; ch = getchar(); } while(ch >=‘0‘ && ch <= ‘9‘) { ans *= 10; ans += ch - ‘0‘; ch = getchar(); } return ans * op; } void euler() { rep(i,2,n) { if(!np[i]) prime[++tot] = i; for(int j = 1;i * prime[j] <= n;j++) { np[prime[j] * i] = 1; if(!(i % prime[j])) break; } } } void C(int a,int b) { rep(i,b+1,a) { int p = i,d = 1; while(p > 1) { while (!(p % prime[d])) ans[d]++,p /= prime[d]; d++; } } rep(i,1,a-b) { int p = i,d = 1; while(p > 1) { while (!(p % prime[d])) ans[d]--,p /= prime[d]; d++; } } } void calc() { int d = 1; while(sum > 1) { while (!(sum % prime[d])) ans[d] += len,sum /= prime[d]; d++; } } int main() { n = read(); euler(); len = n-2; rep(i,1,n) { a = read(); if(!a || a >= n) { printf("0"); return 0; } if(a == -1) { sum++; continue; } a--,C(len,a),len -= a; } if(len) calc(); t[1] = 1,k = 1; rep(i,1,170) { while(ans[i]) { ans[i]--; rep(j,1,k) t[j] *= prime[i]; rep(j,1,k) if(t[j] >= 10) t[j+1] += t[j]/10,t[j] %= 10; while(t[k+1]) k++,t[k+1] += t[k] / 10,t[k] %= 10; } } per(i,k,1) printf("%d",t[i]); enter; return 0; }
HNOI2008 明明的煩惱 (purfer序列 + 組合數學)