思路來源
https://www.cnblogs.com/zwfymqz/p/8869956.html
https://www.luogu.com.cn/blog/xht37/solution-p6086
https://www.luogu.com.cn/blog/hekaiyu/prufer-xu-lie
知識總結
常用於生成樹計數,畢竟都搞出序列了。
一個長度爲n-2的Prufer序列,唯一對應一棵n個點固定形態的無根樹。
T->P(Tree->Prufer)
- 找到編號最小的葉子x
- 設與葉子x相連的點是y,則刪掉x,並在prufer序列尾部加入一個數y
- 重複1,2操作,直到整棵樹只剩下兩個節點,此時prufer序列長度爲n-2
借鑑一下洛谷用戶@hekaiyu的圖片,是Tree->Prufer序列的流程
不難發現,剩的兩個節點裏一定會有一個是n,
最大的根本不會被優先刪掉,所以一般用以n爲根的有根樹和Prufer序列互相轉化比較方便
O(nlogn):
用堆或set維護一下
O(n):
先找到編號最小的葉節點。
每次刪掉它之後,如果產生了新的葉節點且編號比指針指向的更小,則直接繼續刪掉,
否則自增找到下一個編號最小的葉節點。
P->T(Prufer->Tree)
- 把prufer序列的數加入隊列中,設當前隊首爲u
- 維護一個還沒有連邊的點集set(初始放入1-n),找到點集其中編號最小的,且沒有在當前隊列中出現的元素v
- 給u,v連邊,並在點集裏刪掉v,在隊列裏彈出u
- 最終,在點集set中剩下兩個節點,給它們之間連一條邊
其實就是剛纔順序構造,但是變加邊爲刪邊的過程,
考慮原樹轉prufer序列時,每次擼掉一個最小的葉子,
prufer轉回原樹的時候就找到最小的葉子接回去,並刪掉對應的度數
後來就是接樹枝的過程,但由於在樹中加邊的過程中減掉了對應的度數,
在這個過程中,參與接樹枝的x此時度數已經爲0了
O(nlogn):
用堆或set維護一下
O(n):
具體而言,指針指向編號最小的度數爲 1 的節點。
每次將它與當前枚舉到的 Prufer 序列的點連接之後,如果產生了新的度數爲 1 的節點且編號比指針指向的更小,
則直接繼續將它與下一個 Prufer 序列的點連接,否則自增找到下一個編號最小的度數爲 1 的節點。
Prufer序列性質
prufer序列中,點u出現的次數,等於點u在樹中的度數-1
n個點的無根樹,唯一對應長度爲n-2的prufer序列,序列每個數都在1到n的範圍內。
Cayley定理:n個點的無向完全圖的生成樹的計數:,即n個點的有標號無根樹的計數
n個節點的度依次爲的無根樹共有個,因爲此時Prufer編碼中的數字i恰好出現di−1次,(n−2)!是總排列數
n個點的有標號有根樹的計數:
一點個人的理解
1.分有沒有被刪討論一下,
被刪的點被刪的時候還連有一個點,加入prufer序列的點不是自己;
沒被刪的那兩個點,在樹中都還剩一個度,其餘的度在prufer序列裏都是自己
2.顯然
3.prufer序列任意位置都有[1,n]共n種選擇,任意填法都對應一棵無根樹
4.已知要填哪些數後,(n-2)!除掉每個內部的順序(di-1)!
5.給一棵樹指定根,共n種方法
模板題 P6086 【模板】Prufer 序列
請實現 Prufer 序列和無根樹的相互轉化。
爲方便你實現代碼,儘管是無根樹,我們在讀入時仍將 n(n<=5e6) 設爲其根。
對於一棵無根樹,設 爲其父親序列(fi 表示 i 在 n 爲根時的父親),設爲其 Prufer 序列。
另外,對於一個長度爲 m 的序列 ,我們設其權值爲 。
輸入格式
第一行兩個整數 n,m,表示樹的點數和轉化類型。
若 m = 1,第二行一行 n-1 個整數,表示父親序列。
若 m = 2,第二行一行 n-2 個整數,表示 Prufer 序列。
輸出格式
若 m = 1,一行一個整數,表示給出的父親序列對應的 Prufer 序列的權值。
若 m = 2,一行一個整數,表示給出的 Prufer 序列對應的父親序列的權值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e6 + 7;
int n, o, f[N], p[N], d[N];
ll ans;
//實際構造時,把其看成以n爲根的有根樹更方便
//度指的是兒子->父親 父親+入度
//Tree->Prefer
inline void TP() {
for (int i = 1; i < n; i++) scanf("%d",&f[i]),++d[f[i]];
for (int i = 1, j = 1; i <= n - 2; i++, j++) {
while (d[j]) ++j;//找到編號最小的葉節點
p[i] = f[j];
while (i <= n - 2 && !--d[p[i]] && p[i] < j) p[i+1] = f[p[i]], ++i;//新葉節點且編號比當前葉節點更小
}
for (int i = 1; i <= n - 2; i++) ans ^= 1ll * i * p[i];
}
//Prufer->Tree
inline void PT() {
for (int i = 1; i <= n - 2; i++) scanf("%d",&p[i]), ++d[p[i]];//出現次數=度-1 度=0爲葉
p[n-1] = n;//最大的一定是最後兩個點其一
for (int i = 1, j = 1; i < n; i++, j++) {
while (d[j]) ++j; f[j] = p[i];//把最小的葉往prufer序列第一個點上接 對應減掉度數
while (i < n && !--d[p[i]] && p[i] < j) f[p[i]] = p[i+1], ++i;//如果序列第一個點減掉度數後產生了新的更小的葉 就往序列下一個點上接
}
for (int i = 1; i < n; i++) ans ^= 1ll * i * f[i];
}
int main() {
scanf("%d%d",&n,&o), o == 1 ? TP() : PT(), printf("%lld\n",ans);
return 0;
}
模板題 P2290 [HNOI2004]樹的計數
一個有n(1<=n<=150)個節點的樹,設它的節點分別爲1-n,第i個節點的度數爲di,
問滿足這樣的條件的不同的樹有多少棵,保證滿足條件的樹不超過1e17個。
①特判n=1 ②特判di-1之和是不是等於n-2 ③特判是否存在度數爲0的孤立點使樹不連通
代公式,,可以理解爲n-2個位置,依次放入n種數第i種數有di-1個
分步放,選出一些位置放di-1,然後再放下一個,中間結果就不會爆ll了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=155;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
int n,sum,d[N];
ll c[N][N];
void init(){
rep(i,0,N-2){
c[i][0]=1;
}
rep(i,1,N-2){
rep(j,1,i){
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
}
int main(){
scanf("%d",&n);
rep(i,1,n){
scanf("%d",&d[i]);
sum+=d[i]-1;
if(!d[i] && n!=1){//判存在孤立點的情形
return printf("0"),0;
}
}
if(n==1){//判一個點的情形
return printf("%d",d[1]==0),0;
}
if(sum!=n-2){//判prufer序列出現總次數
return printf("%d",0),0;
}
init();
ll ans=1;
//n-2個位置 依次放入若干種di-1個相同的數 可以分步放 這樣就不會爆ll了
rep(i,1,n){
ans=ans*c[sum][d[i]-1];
sum-=d[i]-1;
}
printf("%lld",ans);
return 0;
}
模板題 P4430 小猴打架
一開始森林裏面有N(N<=1e6)只互不相識的小猴子,它們經常打架,但打架的雙方都必須不是好朋友。
每次打完架後,打架的雙方以及它們的好朋友就會互相認識,成爲好朋友。
經過N-1次打架之後,整個森林的小猴都會成爲好朋友。
現在的問題是,總共有多少種不同的打架過程,輸出方案數mod 9999991。
比如當N=3時,就有{1-2,1-3}{1-2,2-3}{1-3,1-2}{1-3,2-3}{2-3,1-2}{2-3,1-3}六種不同的打架過程。
由Cayley定理,次方種樹形,
對於一種樹形,n-1條邊的加邊順序對應了不同的打架過程,再乘(n-1)!
n = int(input())
mod =9999991
ans =1
for i in range(n-2):
ans = ans * n % mod
for i in range(1,n):
ans = ans * i % mod
print(ans)