CF1054G New Road Network

现在你要构造一棵\(n\)个点的树。

题目给出\(m\)个集合,要求每个集合内的点在构造出的树中形成连通块。

\(n,m\le 2000\)


\(A_i\)为包含\(i\)的集合的集合。

如果某个集合只在一个\(A_i\)中出现过,则删去这个集合。先进行这个操作。

如果有解,对于一个叶子节点\(u\),其父亲为\(v\)。可以发现此时一定有\(A_u\subseteq A_v\)

反过来,如果有解,任意找到一对\((u,v)\)满足\(A_u\subseteq A_v\),把\(u\)的父亲钦定为\(v\)。然后删去\(u\),并且删去只在一个\(A_i\)中出现过的集合,变成子问题继续搞下去。这样最终一定能找到一个解。

如果这样找不到解,说明无解。

模拟这个过程即可,可以做到\(O(\frac{n^2m}{\omega})\)的时间。

另一个不太好证明的做法:造一个无向图,\((u,v)\)边权为\(|A_u \bigcap A_v|\),做最大生成树。直接判断这个生成树是否合法,如果不合法一定无解。


using namespace std;
#include <bits/stdc++.h>
#define N 2005
int n,m;
char str[N][N];
int c[N];
bitset<N> b[N];
int e[N][N];
pair<int,int> ed[N];
int cnt;
pair<int,int> q[N*N];
bool vis[N];
bool doit(){
	int head=1,tail=0;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
			if (i!=j && (b[i]&b[j])==b[i])
				e[i][j]=1,q[++tail]={i,j};
			else
				e[i][j]=0;
	memset(vis,0,sizeof(bool)*(n+1));
	cnt=0;
	while (head<=tail && cnt<n-1){
		int u=q[head].first,v=q[head].second;
		++head;
		if (vis[u] || vis[v])
			continue;
		ed[++cnt]={u,v};
		vis[u]=1;
		for (int i=1;i<=m;++i)
			if (b[u][i]){
				if (--c[i]==1)
					b[v][i]=0;
			}
		for (int i=1;i<=n;++i)
			if (!vis[i] && i!=v && !e[v][i] && (b[v]&b[i])==b[v])
				e[v][i]=1,q[++tail]={v,i};
	}
	return cnt==n-1;
}
int main(){
//	freopen("in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d",&n,&m);
		for (int i=1;i<=n;++i)
			b[i].reset();
		for (int i=1;i<=m;++i){
			scanf("%s",str[i]+1);
			c[i]=0;
			for (int j=1;j<=n;++j)
				c[i]+=(str[i][j]=='1');
			if (c[i]>1)
				for (int j=1;j<=n;++j)
					if (str[i][j]=='1')
						b[j][i]=1;
		}
		if (!doit())
			printf("NO\n");
		else{
			printf("YES\n");
			for (int i=1;i<n;++i)
				printf("%d %d\n",ed[i].first,ed[i].second);
		}
	}
	return 0;
}
posted @ 2021-02-26 19:33  jz_597  阅读(81)  评论(0编辑  收藏  举报