【CF611H】New Year and Forgotten Tree(網路流)
阿新 • • 發佈:2020-10-26
- 有一棵\(n\)個點的樹,編號為\(1\sim n\)。
- 給出每條邊兩個點的編號十進位制下的位數。
- 求構造一棵合法的樹,或判斷無解。
- \(n\le2\times10^5\)
核心點
考慮對於每一種位數相同的點,我們都選取一個點作為該位數的核心點。
可以證明,任何一種合法方案必然能夠轉化成沒有兩個非核心點相連的情況。
這是我們做這道題的基礎。
既然沒有兩個非核心點相連,那麼就只有核心點與核心點、核心點與非核心點兩種情況了。
核心點與核心點
顯然核心點與核心點之間的連邊構成的必然是一棵樹。
因此我們直接暴搜樹的形態。
正常的方法應該是利用\(prufer\)序列直接\(O(m^{m-2})\)
不正常的方式(我的做法)也可以是強制\(1\)號點為根,\(O((m-1)^{m-1})\)列舉其餘每個點的父節點,然後暴力判一下這是不是一棵樹即可。
這個暴搜本來就有很多寫法,畢竟\(m=6\)隨便瞎跑都能跑過去,主要看個人喜好。
核心點與非核心點
這類邊才是本道題的關鍵。
對於同種位數的點之間的連邊,就每次取一個非關鍵點連向關鍵點。
對於不同位數,顯然每一種位數的一個非關鍵點只會連一次邊,因此我們很容易算出該種位數的關鍵點需要恰好連多少條邊。
每一條邊有兩種選擇,每一種位數又有一個容量限制,容易想到網路流,且有解的條件就是能流滿。
具體建圖,從超級源向代表邊的點連邊,從代表邊的點向代表這條邊連的兩種位數
注意到連向兩種位數相同的邊可以合在一起,所以種數是\(C_m^2\)的。
發現網路流總點數很少,隨便跑。
程式碼
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 200000 #define mp make_pair #define pb push_back using namespace std; int n,m,tot,tn[10],ps[10],p[10][10],t[10][10],X[N+5],Y[N+5]; vector<pair<int,int> > res; namespace NetFlow//網路流 { #define PS 30 #define ES 100 #define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f) int ee,lnk[PS+5],cur[PS+5],d[PS+5],q[PS+5];struct edge {int F,to,nxt;}e[2*ES+5]; I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);} I bool BFS() { RI i,k,H=1,T=1;for(i=1;i<=tot;++i) d[i]=0;d[q[1]=1]=1;W(H<=T&&!d[2]) for(i=lnk[k=q[H++]];i;i=e[i].nxt) e[i].F&&!d[e[i].to]&&(d[q[++T]=e[i].to]=d[k]+1); return d[2]&&memcpy(cur,lnk,sizeof(lnk)),d[2]; } I int DFS(CI x=1,RI f=1e9) { if(!f||x==2) return f;RI i,t,res=0;for(i=cur[x];i;i=e[i].nxt) { if(d[e[i].to]^(d[x]+1)||!(t=DFS(e[i].to,min(f,e[i].F)))) continue; if(e[i].F-=t,e[((i-1)^1)+1].F+=t,res+=t,!(f-=t)) break; }return cur[x]=i,res; } I int MaxFlow() {RI f=0;W(BFS()) f+=DFS();return f;}//最大流 int M,Mx[10];I void Build()//建圖 { RI i,j;for(ee=0,i=1;i<=tot;++i) lnk[i]=0;for(i=1;i<=m;++i) Mx[i]=0;//清空 for(i=1;i<=m;++i) for(j=i+1;j<=m;++j) t[i][j]&&(Mx[i]+=t[i][j],Mx[j]+=t[i][j], Add(1,p[i][j]+m+2,t[i][j]),Add(p[i][j]+m+2,i+2,t[i][j]),Add(p[i][j]+m+2,j+2,t[i][j]),0);//連邊 for(M=0,i=1;i<=m;++i) M+=(Mx[i]-=((i^m)?tn[i+1]:n+1)-ps[i]-1),Add(i+2,2,Mx[i]);//連邊,M統計總流量 } I void Work()//把流法轉化成答案 { RI i,j,k,w;for(i=1;i<=m;++i) for(j=i+1;j<=m;++j) if(t[i][j]) for(k=lnk[p[i][j]+m+2];k;k=e[k].nxt) { if(e[k].to==i+2) for(w=1;w<=t[i][j]-e[k].F;++w) res.pb(mp(tn[i],++ps[j]));//從i的核心點連出邊 if(e[k].to==j+2) for(w=1;w<=t[i][j]-e[k].F;++w) res.pb(mp(++ps[i],tn[j]));//從j的核心點連出邊 } } I bool Check()//檢驗 { Build();for(RI i=1;i<=m;++i) if(Mx[i]<0) return 0; return MaxFlow()==M?(Work(),1):0;//是否能流滿 } } namespace DFS//暴搜樹的形態 { int fa[10],vis[10];I bool IsTree()//檢驗是否為樹 { memset(vis,0,sizeof(vis));for(RI i=2,x;i<=m;++i) {x=i;W(x^1&&vis[x]^i) vis[x]=i,x=fa[x];if(x^1) return 0;}return 1;//如果能跳父親跳到根 } I bool dfs(CI x)//搜尋 { if(x>m) return IsTree()&&NetFlow::Check(); for(RI i=1;i<=m;++i) if(x^i&&t[x][i]) { fa[x]=i,--t[x][i],--t[i][x],res.pb(mp(tn[x],tn[i])); if(dfs(x+1)) return 1;++t[x][i],++t[i][x],res.pop_back(); }return 0; } } int main() { RI i,j,k=0;char s1[10],s2[10];for(scanf("%d",&n),i=1;i^n;++i)//讀入 cin>>s1>>s2,++t[X[i]=strlen(s1)][Y[i]=strlen(s2)],X[i]^Y[i]&&++t[Y[i]][X[i]]; for(k=m=1;k<=n;++m) k*=10;for(--m,k=0,i=1;i<=m;++i) for(j=i+1;j<=m;++j) p[i][j]=++k;//給每種邊標號 for(i=1;i<=m;++i) for(ps[i]=tn[i]=(i^1)?tn[i-1]*10:1,j=1;j<=t[i][i];++j) res.pb(mp(tn[i],++ps[i]));//對於同種位數,向關鍵點連邊 if(tot=p[m-1][m]+m+2,!DFS::dfs(2)) return puts("-1"),0;//無解輸出-1 for(i=0;i<n-1;++i) printf("%d %d\n",res[i].first,res[i].second);return 0;//輸出合法方案 }