prufer序列
prufer序列
-
用途: 将带标号的树用唯一的整数序列表示出来,证明凯莱公式。
-
构造方法:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复\(n-2\)次后就只剩下两个结点,算法结束。
举个栗子(本图来自baoziwu2,侵删)
显然可以有一个用堆做的方法,时间复杂度\(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 \]学这玩意就是为了这个式子
本文来自博客园,作者:CuFeO4,转载请注明原文链接:https://www.cnblogs.com/hzoi-Cu/p/18284130