Prufer序列(知識總結+板子整理)

思路來源

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)

  1. 找到編號最小的葉子x
  2. 設與葉子x相連的點是y,則刪掉x,並在prufer序列尾部加入一個數y
  3. 重複1,2操作,直到整棵樹只剩下兩個節點,此時prufer序列長度爲n-2

prufer

借鑑一下洛谷用戶@hekaiyu的圖片,是Tree->Prufer序列的流程

不難發現,剩的兩個節點裏一定會有一個是n,

最大的根本不會被優先刪掉,所以一般用以n爲根的有根樹和Prufer序列互相轉化比較方便

 

O(nlogn):

用堆或set維護一下

O(n):

先找到編號最小的葉節點。

每次刪掉它之後,如果產生了新的葉節點且編號比指針指向的更小,則直接繼續刪掉,

否則自增找到下一個編號最小的葉節點。

 

P->T(Prufer->Tree)

  1. 把prufer序列的數加入隊列中,設當前隊首爲u
  2. 維護一個還沒有連邊的點集set(初始放入1-n),找到點集其中編號最小的,且沒有在當前隊列中出現的元素v
  3. 給u,v連邊,並在點集裏刪掉v,在隊列裏彈出u
  4. 最終,在點集set中剩下兩個節點,給它們之間連一條邊

其實就是剛纔順序構造,但是變加邊爲刪邊的過程,

考慮原樹轉prufer序列時,每次擼掉一個最小的葉子,

prufer轉回原樹的時候就找到最小的葉子接回去,並刪掉對應的度數

後來就是接樹枝的過程,但由於在樹中加邊的過程中減掉了對應的度數,

在這個過程中,參與接樹枝的x此時度數已經爲0了

 

O(nlogn):

用堆或set維護一下

O(n):

具體而言,指針指向編號最小的度數爲 1 的節點。

每次將它與當前枚舉到的 Prufer 序列的點連接之後,如果產生了新的度數爲 1 的節點且編號比指針指向的更小,

則直接繼續將它與下一個 Prufer 序列的點連接,否則自增找到下一個編號最小的度數爲 1 的節點。

 

Prufer序列性質

  1. prufer序列中,點u出現的次數,等於點u在樹中的度數-1

  2. n個點的無根樹,唯一對應長度爲n-2的prufer序列,序列每個數都在1到n的範圍內。

  3. Cayley定理:n個點的無向完全圖的生成樹的計數:n^{n-2},即n個點的有標號無根樹的計數

  4. n個節點的度依次爲d1,d2,\dots,dn的無根樹共有\frac{(n-2)!}{ \prod_{i=1}^n(d_i-1)!}個,因爲此時Prufer編碼中的數字i恰好出現di−1次,(n−2)!是總排列數

  5. n個點的有標號有根樹的計數:n^{n-2}*n=n^{n-1}

一點個人的理解

1.分有沒有被刪討論一下,

被刪的點被刪的時候還連有一個點,加入prufer序列的點不是自己;

沒被刪的那兩個點,在樹中都還剩一個度,其餘的度在prufer序列裏都是自己

2.顯然

3.prufer序列任意位置都有[1,n]共n種選擇,任意填法都對應一棵無根樹

4.已知要填哪些數後,(n-2)!除掉每個內部的順序(di-1)!

5.給一棵樹指定根,共n種方法

 

模板題 P6086 【模板】Prufer 序列

請實現 Prufer 序列和無根樹的相互轉化。

爲方便你實現代碼,儘管是無根樹,我們在讀入時仍將 n(n<=5e6) 設爲其根。

對於一棵無根樹,設 f_{1\dots n-1}爲其父親序列(fi​ 表示 i 在 n 爲根時的父親),設p_{1 \dots n-2}爲其 Prufer 序列

另外,對於一個長度爲 m 的序列 a_{1 \dots m}​,我們設其權值爲 \operatorname{xor}_{i = 1}^m i \times a_i

輸入格式

第一行兩個整數 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的孤立點使樹不連通

代公式,\frac{(n-2)!}{ \prod_{i=1}^n(d_i-1)!},可以理解爲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^{n-2}次方種樹形,

對於一種樹形,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)

 

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