Codeforces 611H - New Year and Forgotten Tree(二分图多重匹配)

Codeforces 题目传送门 & 洛谷题目传送门

首先我们将所有十进制下位数相同的点看作一种颜色,这样题目转化为,给定 \(m\le 6\) 种颜色、每种颜色的点的个数 \(b_i\) 以及每两种颜色之间连的边的条数 \(c_{i,j}\),要求构造出一棵符合要求的树。

考虑怎样解决上面的问题,这里有一个结论,对于每种颜色我们钦定一个点为“关键点”(方便起见,对于颜色 \(i\),我们钦定 \(10^{i-1}\) 为第 \(i\) 种颜色的关键点),那么,如果存在合法的树,一定存在一种情况,使得任意一条边都有一个点为关键点。至于证明,不太会严谨证明,不过大概想想应该可以想通,大概就是对于每条边 \((u,v)\),如果 \(u,v\) 都不是关键点,那么可以将 \(u\) 改接到 \(v\) 的颜色的关键点,或者将 \(v\) 改接到 \(u\) 的颜色的关键点,总能找到一种调整方法使得不存在 \(u,v\) 都不是关键点的边 \((u,v)\)

不难发现对于符合上面的条件的图,如果只考虑 \(m\) 个关键点,那么这 \(m\) 个点形成的图也是一棵 \(m\) 个点的树,由于 \(m\) 很小,因此考虑根据 prufer 序列将这 \(m^{m-2}\) 棵树枚举一遍,接下来考虑怎样检验对于一棵关键点的生成树,是否存在一种将非关键点连向关键点的方案,使得对于任意两种颜色 \(i,j\)\(i,j\) 之间连的边数恰为 \(c_{i,j}\),我们不妨将这个问题转化一下,可以得到如下的模型:

  • 对于每种颜色,共有 \(b_i-1\) 个非关键点,对于这 \(b_i-1\) 个关键点中的每一个点 \(u\),我们都要选择一种颜色 \(j\),并将 \(u\) 连向 \(j\) 的关键点,这样会使得 \(u,j\) 间边的个数多 \(1\)
  • 对于每个颜色之间的二元组 \((i,j)\) 都还需连 \(a_{i,j}\) 条边,其中 \(a_{i,j}\)\(c_{i,j}\) 减去 \(i,j\) 在枚举的生成树上是否存在边。

敏感的同学应该可以发现,这不就一个二分图多重匹配吗?我们将所有颜色对 \((i,j)\) 看作左部点,连一条从源点像这样的 \((i,j)\),容量为 \(a_{i,j}\) 的边,所有颜色 \(i\) 看作右部点,连一条从 \(i\) 到汇点,容量为 \(b_i-1\) 的边,然后检验这张图是否存在完备匹配即可。输出方案就 dinic 跑遍最大流即可。时间复杂度 \(\mathcal O(\text{能过})\)

const int MAXV=30;
const int MAXE=300*2;
const int INF=0x3f3f3f3f;
int n,m,p[8],q[8],cnt[8];
int S=29,T=30,hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],cap[MAXE+5],ec=1;
void init(){memset(hd,0,sizeof(hd));ec=1;}
void adde(int u,int v,int f){
	to[++ec]=v;cap[ec]=f;nxt[ec]=hd[u];hd[u]=ec;
	to[++ec]=u;cap[ec]=0;nxt[ec]=hd[v];hd[v]=ec;
} int dep[MAXV+5],now[MAXV+5];
bool getdep(){
	memset(dep,-1,sizeof(dep));dep[S]=0;
	queue<int> q;q.push(S);now[S]=hd[S];
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=cap[e];
			if(!~dep[y]&&z){
				dep[y]=dep[x]+1;
				q.push(y);now[y]=hd[y];
			}
		}
	} return ~dep[T];
}
int getflow(int x,int f){
	if(x==T) return f;int ret=0;
	for(int &e=now[x];e;e=nxt[e]){
		int y=to[e],z=cap[e];
		if(dep[y]==dep[x]+1&&z){
			int w=getflow(y,min(f-ret,z));
			cap[e]-=w;cap[e^1]+=w;ret+=w;
			if(ret==f) return ret;
		}
	} return ret;
}
int dinic(){
	int ret=0;
	while(getdep()) ret+=getflow(S,INF);
	return ret;
}
int num[8][8],cc[8][8],pru[8],deg[8],vis[8];
void solve(){
	memset(deg,0,sizeof(deg));
	memset(vis,0,sizeof(vis));
	memset(cc,0,sizeof(cc));
	for(int i=1;i<=m-2;i++) deg[pru[i]]++;
	for(int i=1;i<=m-2;i++){
		int pos=0;
		for(int j=1;j<=m;j++){
			if(!deg[j]&&!vis[j]){pos=j;break;}
		} cc[min(pos,pru[i])][max(pos,pru[i])]++;
//		eprintf("%d %d\n",pos,pru[i]);
		deg[pru[i]]--;vis[pos]=1;
	} int u1=1,u2=m;
	while(vis[u1]) u1++;
	while(vis[u2]) u2--;
	cc[u1][u2]++;
//	eprintf("%d %d\n",u1,u2);
	int cur=0;init();
	for(int i=1;i<=m;i++) for(int j=i;j<=m;j++){
		if(cc[i][j]>num[i][j]) return;cur++;
		adde(S,cur,num[i][j]-cc[i][j]);
		adde(cur,m*(m+1)/2+i,INF);
		adde(cur,m*(m+1)/2+j,INF);
	} for(int i=1;i<=m;i++) adde(m*(m+1)/2+i,T,cnt[i]-1);
	if(dinic()==n-m){
		for(int i=1;i<=m;i++) for(int j=i;j<=m;j++){
			if(cc[i][j]) printf("%d %d\n",p[i],p[j]);
		}
		for(int i=1;i<=m;i++) q[i]=p[i];cur=0;
		for(int i=1;i<=m;i++) for(int j=i;j<=m;j++){
			cur++;
			for(int e=hd[cur];e;e=nxt[e]){
				int y=to[e],z=cap[e^1];
				if(y==S) continue;
				if(y-m*(m+1)/2==i){
					while(z--) printf("%d %d\n",++q[i],p[j]);
				} else {
					while(z--) printf("%d %d\n",++q[j],p[i]);
				}
			}	
		}
		exit(0);
	}
}
void dfs(int x){
	if(x==m-1) return solve();
	for(int i=1;i<=m;i++) pru[x]=i,dfs(x+1);
}
int main(){
	scanf("%d",&n);for(int cur=1;cur<=n;cur*=10) p[++m]=cur;
	for(int i=1;i<m;i++) cnt[i]=p[i+1]-p[i];cnt[m]=n-p[m]+1;
	for(int i=1;i<n;i++){
		static char s1[9],s2[9];
		scanf("%s%s",s1+1,s2+1);
		int l1=strlen(s1+1),l2=strlen(s2+1);
		if(l1>l2) l1^=l2^=l1^=l2;
		num[l1][l2]++;
	} if(m==1){
		if(num[1][1]!=n-1) return puts("-1"),0;
		for(int i=1;i<n;i++) printf("%d %d\n",i,i+1);
		return 0;
	} dfs(1);puts("-1");
	return 0;
}
posted @ 2021-04-17 13:07  tzc_wk  阅读(58)  评论(1)    收藏  举报