CF1054G New Road Network

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

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

(n,mle 2000)


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

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

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

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

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

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

另一个不太好证明的做法:造一个无向图,((u,v))边权为(|A_u igcap 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
");
		else{
			printf("YES
");
			for (int i=1;i<n;++i)
				printf("%d %d
",ed[i].first,ed[i].second);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/14453748.html