题目分析:
判断是否存在两个集合,使这两个集合的交集不为空,并且一个当中的某些元素也在另一个当中出现过。
算法分析:
首先来看暴力:很容易想到根据题目所给的信息来枚举,每次判断两个集合是否能满足条件。期望得分:40。
然后来看正解:容易发现,如果按照所给集合的大小从大到小排序,那么前一个集合一定不会是后一个集合的真子集。这时可以用染色法来求解。
考虑某一个集合,把它能到的点(也就是这个人会做的题的编号)给染成一种颜色,一层一层往下覆盖。
那么怎么求答案呢?考虑某一个集合,如果这个集合能覆盖到两种颜色,那么必然说明该集合和某一个覆盖这两种颜色之一的集合是答案。但是具体是哪一个呢?肯定是覆盖的颜色小的那一个集合喽(不明白的话代码里有详解)。
但是,就差最后一步了!如果某一个集合覆盖的点颜色为0的话,那么就输出这个集合。
代码+注释详解
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#define re register
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48) ; ch=getchar();}
return x*f;
}
const int M = 1e6+10;
vector<int>v[M]; //要用vector才能不爆空间
int T,n;
int col[M],k[M],rak[M];
bool cmp(int x,int y) {return k[x] > k[y];} //自定义一个比较函数,按照k的值从大到小排序
void work(){
n=read();
memset(col,0,sizeof(col));
memset(k,0,sizeof(k));
memset(rak,0,sizeof(rak)); //一定要清空数组,考试的时候我就没清空直接爆零
for(re int i(1) ; i<=n ; ++i){
k[i] = read();
v[i].clear(); //同样需要清空vector
for(re int j(1) ; j<=k[i] ; ++j){
int x=read();
v[i].push_back(x); //把i这个点会做的题给存入vector
}
rak[i] = i; //i这个点的编号
}
sort(rak+1,rak+n+1,cmp); //按k的大小,从大到小排序
int cnt=1;
for(re int i(0) ; i<v[rak[1]].size() ; ++i) col[v[rak[1]][i]] = rak[1]; //先预处理出最大的集合,放在循环里也行
k[0]=1e9; //特殊情况,下面说
for(re int i(2) ; i<=n ; ++i){
cnt++;
int color = 0,pd = 0;
for(re int j(0) ; j<v[rak[i]].size() ; ++j){ //核心部分,先遍历这个vector
if(color != col[v[rak[i]][j]] && pd == 1){ //说明已经有两种颜色了
printf("YES\n%d %d\n",rak[i],k[col[v[rak[i]][j]]]<k[color]?col[v[rak[i]][j]]:color);
//重点来了:如果color都不是0,那么输出较小的那一个集合
//如果color所能覆盖是0,也就是上面的特殊情况,那么就输出color
return;
}
if(pd == 0) pd=1;
color = col[v[rak[i]][j]]; //把颜色赋值为第一个点下面覆盖的颜色
col[v[rak[i]][j]] = rak[i]; //注意,把这些点的颜色标记为rak,后面才能继续做
}
}
printf("NO\n"); //循环完都没找到的情况
return;
}
signed main(){
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
T=read();
while(T--) work(); //每一次跑就行了
return 0;
}