prufer序列

prufer序列

  • 用途: 将带标号的树用唯一的整数序列表示出来,证明凯莱公式。

  • 构造方法:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复\(n-2\)次后就只剩下两个结点,算法结束。

    举个栗子(本图来自baoziwu2,侵删)

    image

    显然可以有一个用堆做的方法,时间复杂度\(O(n\log n)\)

    \(code:\)

    点此查看代码
    #include<bits/stdc++.h>
    #include<bits/extc++.h>
    // using namespace __gnu_pbds;
    // using namespace __gnu_cxx;
    using namespace std;
    #define infile(x) freopen(x,"r",stdin)
    #define outfile(x) freopen(x,"w",stdout)
    #define errfile(x) freopen(x,"w",stderr)
    using ll=long long;using ull=unsigned long long;
    char *p1,*p2,buf[1<<20];
    #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
    #ifdef linux
    #define pc putchar_unlocked
    #else
    #define pc putchar
    #endif
    namespace IO{
        template<typename T>inline bool read(T &x){x=0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x<<1)+(x<<3)+(s^48);if(!f) x=~x+1;return true;}
        inline bool read(double &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(long double &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(float &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(string &str){string ().swap(str);char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false; for(;s!=' '&&s!='\n'&&s!=EOF;s=gc())str.push_back(s);return true;}
        inline bool read_line(string &str){string ().swap(str);char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false;for(;s!='\n'&&s!=EOF;s=gc()){str.push_back(s);}return true;}
        inline bool read_line(char *str){int len=0;char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false;for(;s!='\n'&&s!=EOF;s=gc()){str[len]=s;len++;}str[len]='\0';return true;}
        inline bool read(char &s){char x=gc();for(;x==' '||x=='\n';x=gc());if(x==EOF||x==' '||x=='\n')return false;s=x;return true;}
        inline bool read(char *s){int len=0;char x=gc();for(;x==' '||x=='\n';x=gc());if(x==EOF)return false;for(;x!=' '&&x!='\n'&&x!=EOF;x=gc())s[len++]=x;s[len]='\0';return true;}
        template<class T,class... Args> inline bool read(T &x,Args&... args){return (read(x)&&read(args...));}
        template<class T>inline void write(T x){static T st[45];int top=0;if(x<0)x=~x+1,pc('-');do{st[top++]=x%10;}while(x/=10);while(top)pc(st[--top]^48);}
        inline void write(char x){pc(x);}
        inline void write(string s){for(int i=0;s[i];++i) pc(s[i]);}
        inline void write(char *s){int len=strlen(s);for(int i=0;i<len;++i) pc(s[i]);}
        inline void write(const char *s){int len=strlen(s);for(int i=0;i<len;++i) pc(s[i]);}
        template<class T,class... Args> inline void write(T x,Args... args){write(x);write(args...);}
    }using namespace IO;
    const int N = 5e6 + 10;
    int fa[N],d[N],pru[N];
    priority_queue<int,vector<int>,greater<int> > q;
    signed main(){
        #ifndef ONLINE_JUDGE
            infile("in.in");outfile("out.out");
        #else
        #endif
        int n,m;read(n,m);
        ll ans = 0;
        if(m == 1){
            for(int i = 1,x;i < n; ++i) read(x),++d[x],fa[i] = x;
            for(int i = 1;i <= n; ++i)
                if(!d[i]) q.push(i);
            int tot = 0;
            for(int i = 1;i <= n - 2; ++i){
                pru[i] = fa[q.top()];q.pop();
                if(!--d[pru[i]]) q.push(pru[i]);
            }
            for(int i = 1;i <= n - 2; ++i) ans ^= 1ll * i * pru[i];
        }
        else{
            for(int i = 1,x;i <= n - 2; ++i) read(x),pru[i] = x,d[x]++;
            for(int i = 1;i < n; ++i) fa[i] = n;
            for(int i = 1;i <= n; ++i) if(!d[i]) q.push(i);
            for(int i = 1;i <= n - 2; ++i){
                fa[q.top()] = pru[i],q.pop();
                if(!--d[pru[i]]) q.push(pru[i]);
            }
            for(int i = 1;i <= n - 1; ++i) ans ^= 1ll * i * fa[i];
        }
        write(ans);
    }
    

    还有一个\(O(n)\)的构造方法

    考虑删完一个点后,下一次改删哪个点,有两种情况,一是本来度数就为1的点,二是刚刚删完的点的父节点。

    维护一个指针,初始为1,每遇到一个度数为1的节点\(y\),就将其父节点\(x\)记录进\(prufer\)序列中,\(x\)的度数减一,若\(x\)的度数为1,则分类讨论。

    \(x>y\),继续自增。

    \(x<y\),另外处理\(x\),删除它,记录\(x\)的父节点

    重复以上操作。

    \(code:\)

    点此查看代码
    #include<bits/stdc++.h>
    #include<bits/extc++.h>
    // using namespace __gnu_pbds;
    // using namespace __gnu_cxx;
    using namespace std;
    #define infile(x) freopen(x,"r",stdin)
    #define outfile(x) freopen(x,"w",stdout)
    #define errfile(x) freopen(x,"w",stderr)
    using ll=long long;using ull=unsigned long long;
    char *p1,*p2,buf[1<<20];
    #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
    #ifdef linux
    #define pc putchar_unlocked
    #else
    #define pc putchar
    #endif
    namespace IO{
        template<typename T>inline bool read(T &x){x=0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x<<1)+(x<<3)+(s^48);if(!f) x=~x+1;return true;}
        inline bool read(double &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(long double &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(float &x){x=0.0;char s=gc();bool f=true;for(;(s<'0'||'9'<s);s=gc()) {if(s=='-') f=false;if(s==EOF)return false;}for(;'0'<=s&&s<='9';s=gc()) x=(x*10)+(s^48);if(s!='.'){return true;}double res=0.1;s=gc();for(;'0'<=s&&s<='9';res/=10,s=gc()) x+=(s^48)*res;x=f?x:-x;return true;}
        inline bool read(string &str){string ().swap(str);char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false; for(;s!=' '&&s!='\n'&&s!=EOF;s=gc())str.push_back(s);return true;}
        inline bool read_line(string &str){string ().swap(str);char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false;for(;s!='\n'&&s!=EOF;s=gc()){str.push_back(s);}return true;}
        inline bool read_line(char *str){int len=0;char s=gc();for(;s==' '||s=='\n';s=gc());if(s==EOF) return false;for(;s!='\n'&&s!=EOF;s=gc()){str[len]=s;len++;}str[len]='\0';return true;}
        inline bool read(char &s){char x=gc();for(;x==' '||x=='\n';x=gc());if(x==EOF||x==' '||x=='\n')return false;s=x;return true;}
        inline bool read(char *s){int len=0;char x=gc();for(;x==' '||x=='\n';x=gc());if(x==EOF)return false;for(;x!=' '&&x!='\n'&&x!=EOF;x=gc())s[len++]=x;s[len]='\0';return true;}
        template<class T,class... Args> inline bool read(T &x,Args&... args){return (read(x)&&read(args...));}
        template<class T>inline void write(T x){static T st[45];int top=0;if(x<0)x=~x+1,pc('-');do{st[top++]=x%10;}while(x/=10);while(top)pc(st[--top]^48);}
        inline void write(char x){pc(x);}
        inline void write(string s){for(int i=0;s[i];++i) pc(s[i]);}
        inline void write(char *s){int len=strlen(s);for(int i=0;i<len;++i) pc(s[i]);}
        inline void write(const char *s){int len=strlen(s);for(int i=0;i<len;++i) pc(s[i]);}
        template<class T,class... Args> inline void write(T x,Args... args){write(x);write(args...);}
    }using namespace IO;
    const int N = 5e6+10;
    int fa[N],d[N],pru[N];
    signed main(){
        #ifndef ONLINE_JUDGE
            infile("in.in");outfile("out.out");
        #else
        #endif
        int n,m;read(n,m);
        ll ans = 0;
        if(m == 1){
            for(int i = 1,x;i < n; ++i) read(x),fa[i] = x,++d[x];
            for(int i = 1,p = 1,x;i <= n - 2; ++i,++p){
                while(d[p]) ++p;
                pru[i] = fa[x = p];
                while(i <= n - 2 && !--d[pru[i]] && pru[i] < p) pru[++i] = fa[x = fa[x]];
            }
            for(int i = 1;i <= n - 2; ++i) ans ^= 1ll * i * pru[i];
        }
        else{
            for(int i = 1,x;i <= n - 2; ++i) read(x),pru[i] = x,++d[x];
            pru[n - 1] = n;
            for(int i = 1,p = 1,x;i <= n - 1; ++i,++p){
                while(d[p]) ++p;
                fa[x = p] = pru[i];
                while(i <= n - 1 && !--d[pru[i]] && pru[i] < p) fa[x = fa[x]] = pru[++i];
            }
            for(int i = 1;i <= n - 1; ++i) ans ^= 1ll * i * fa[i];
        }
        write(ans);
    }
    

凯莱公式

  • 内容:

    完全图\(k_n\)\(n^{n-2}\)棵生成树

  • 证明:

    任意一个长度为\(n-2\)的值域为\([1,n]\)的整数序列都可以通过\(Prufer\)序列双射对应一个生成树,于是方案数就是\(n^{n-2}\)

图连通方案数

  • 问题:

    一个\(n\)个点\(m\)条边的带标号无向图有\(k\)个连通块。添加\(k-1\)条边使得整个图连通。求方案数。

  • 证明:

    \(s_i\)为第\(i\)个连通块的数量,\(d_i\)为第\(i\)个连通块的度数,由于构造\(prufer\)序列的方案数为

    \[{k-2}\choose {d_1-1,d_2-1,...,d_k-1} \]

    对于第\(i\)个连通块,它的连接方式有\(s_i^{d_i}\)种,因此方案数为

    \[{{k-2}\choose {d_1-1,d_2-1,...,d_k-1}}\times\prod_{i=1}^ks_i^{d_i} \]

    枚举\(d\)序列,方案数有

    \[\sum_{d_i\ge 1,(\sum_{i=1}^kd_i)=2k-2}{{k-2}\choose {d_1-1,d_2-1,...,d_k-1}}\times\prod_{i=1}^ks_i^{d_i} \]

    换元,令\(a_i=d_i-1\),原式为

    \[\sum_{a_i\ge 0,(\sum_{i=1}^ka_i)=k-2}{{k-2}\choose {a_1,a_2,...,a_k}}\times\prod_{i=1}^ks_i^{a_i+1} \]

    利用多元二项式定理

    \[(x_1+x_2+\dots+x_n)^m=\sum\limits_{\sum_{i=1}^n d_i=m}\binom{m}{d_1,\dots,d_n}\prod_{i=1}^nx_i^{d_i} \]

    化简得

    \[(s_1+s_2+...+s_k)^{k-2}\times \prod_{i=1}^{k}s_i \]

    所以为

    \[n^{k-2}\times\prod_{i=1}^ks_i \]

    学这玩意就是为了这个式子

posted @ 2024-07-04 18:36  CuFeO4  阅读(16)  评论(0编辑  收藏  举报