现在你要构造一棵(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;
}