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;
}