【CF611H】New Year and Forgotten Tree(网络流)

点此看题面

  • 有一棵(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;//输出合法方案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF611H.html