- 有一棵(n)个点的树,编号为(1sim n)。
- 给出每条边两个点的编号十进制下的位数。
- 求构造一棵合法的树,或判断无解。
- (nle2 imes10^5)
核心点
考虑对于每一种位数相同的点,我们都选取一个点作为该位数的核心点。
可以证明,任何一种合法方案必然能够转化成没有两个非核心点相连的情况。
这是我们做这道题的基础。
既然没有两个非核心点相连,那么就只有核心点与核心点、核心点与非核心点两种情况了。
核心点与核心点
显然核心点与核心点之间的连边构成的必然是一棵树。
因此我们直接暴搜树的形态。
正常的方法应该是利用(prufer)序列直接(O(m^{m-2}))枚举。
不正常的方式(我的做法)也可以是强制(1)号点为根,(O((m-1)^{m-1}))枚举其余每个点的父节点,然后暴力判一下这是不是一棵树即可。
这个暴搜本来就有很多写法,毕竟(m=6)随便瞎跑都能跑过去,主要看个人喜好。
核心点与非核心点
这类边才是本道题的关键。
对于同种位数的点之间的连边,就每次取一个非关键点连向关键点。
对于不同位数,显然每一种位数的一个非关键点只会连一次边,因此我们很容易算出该种位数的关键点需要恰好连多少条边。
每一条边有两种选择,每一种位数又有一个容量限制,容易想到网络流,且有解的条件就是能流满。
具体建图,从超级源向代表边的点连边,从代表边的点向代表这条边连的两种位数的两点连边,从代表位数的点向超级汇连边。
注意到连向两种位数相同的边可以合在一起,所以种数是(C_m^2)的。
发现网络流总点数很少,随便跑。
代码:(O()能过())
#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
",res[i].first,res[i].second);return 0;//输出合法方案
}