Prufer序列 、 Cayley公式

Prufer序列:

一种将带标号的树用一个唯一的整数序列表示的方法。

prufer序列的构造:

每次将树中编号最小的叶子节点删除,将与它连接的点(显然唯一)加入序列。
n-2次操作之后,树中只剩下两个节点,操作结束,得到prufer序列。

对树构造prufer序列的两种做法
1.堆,存入所有叶子,每次取出编号最小的叶子。复杂度带log。
2.指针,指向编号最小的叶子,删除之后如果产生新叶子且编号更小则继续删,否则指针++,直到找到下一个叶子节点。复杂度线性。

prufer序列还原树
根据prufer序列,可以得到树上每个点的度数,知道哪些是叶子。
也可以得到度数最小的叶子的编号,这个叶子一定与prufer序列的第一个数连接。
还原这两个点,然后减少这两个点的度数,重复操作即可。
1.堆做法:编号最小的叶子用堆来找,每次操作减少度数如果新增了叶子,也加入堆中。复杂度带log。
2.指针:产生新叶子的时候,判断这个叶子与指针的大小关系,与构造prufer序列类似。复杂度线性。

性质:

1.prufer序列和树一一对应。
2.大小为n的树,prufer序列的长度为n-2。树中剩下的两个节点,其中一个一定是编号最大的点n。
3.树中每个节点在prufer序列中的出现次数为该点的度数-1

加边使图连通的方案数:

n个点m条边,k个连通块,添加k-1条边使得图连通,求加边方案数。

结论:
nk-2 i = 1 k s i \prod _{i=1}^ksi
其中k表示连通块数量,si表示每个连通块点的个数(特判一下k=1的情况,这时候不用乘点数)


Cayley公式:

n个点的完全图,生成树有nn-2

简单证明:

根据prufer序列,任意长度为n-2,值域为[1,n]的序列都可以唯一对应一棵树。
根据乘法原理,序列的种类有nn-2种,因此方案数为nn-2


UVA10843 Anne’s game

题意:

给定n,表示有一个n个点的完全图,问有多少种生成树

数据范围:n<=100

解法:

根据cayley公式,方案数为nn-2,直接计算即可

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
const int mod=2000000011;
int ppow(int a,int b,int mod){
    int ans=1%mod;a%=mod;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
signed main(){
    int T;cin>>T;
    for(int cas=1;cas<=T;cas++){
        int x;cin>>x;
        cout<<"Case #"<<cas<<": ";
        if(x<=2)cout<<1<<endl;
        else cout<<ppow(x,x-2,mod)<<endl;
    }
    return 0;
}

Codeforces156 D. Clues

题意:

给定n个点m条边的无向图,要求加入最少的边使得图连通
求加边方案数,答案对k取模

数据范围:n,m<=1e5,k<=1e9

解法:

加边使得图连通的方案数,套结论就行了:
nk-2 i = 1 k s i \prod _{i=1}^ksi
其中k表示连通块数量,si表示每个连通块点的个数(特判一下k=1的情况,这时候不用乘点数)

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
int pre[maxm];
int num[maxm];
int n,m,mod;
int ppow(int a,int b,int mod){
    int ans=1%mod;a%=mod;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
int ffind(int x){
    return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
signed main(){
    cin>>n>>m>>mod;
    for(int i=1;i<=n;i++){
        pre[i]=i;
        num[i]=1;
    }
    for(int i=1;i<=m;i++){
        int a,b;cin>>a>>b;
        int x=ffind(a),y=ffind(b);
        if(x!=y){
            pre[x]=y;
            num[y]+=num[x];
        }
    }
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(pre[i]==i){
            cnt++;
        }
    }
    if(cnt==1){
        cout<<1%mod<<endl;//1也要取模,因为模数可能为1
    }else{
        int ans=ppow(n,cnt-2,mod);
        for(int i=1;i<=n;i++){
            if(pre[i]==i){
                ans=ans*num[i]%mod;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/107888918