自己现场想的屑解法,望指点。
Description
这种求集合交、并、包含的问题画一个 Venn 图对思考很有帮助,我现场用这种方法很快切了这道(有和我一样第一道没拿满分第二道拿满了的吗)。
用这种方式,我们来转换一下问题:
给若干个集合,输出两个有交集但没有包含关系的集合。
像这样:
Solution
首先方便起见,我们对所有集合按元素个数 k k k 降序排序。这样的好处是:我们按顺序处理每个集合,之前的集合只有可能包含当前集合,而不可能被当前集合包含,一定程度地简化了问题。
接着,我们每次处理一个集合。我们可以数一下之前处理过的集合中,与它有交的集合个数 c n t cnt cnt。分为 3 种情况:
-
c n t = 0 cnt=0 cnt=0
没有集合与其有交,那就先放这不管。
-
c n t = 1 cnt=1 cnt=1
有且仅有一个集合与其有交,设它为 A A A,当前集合为 B。那么检查 B ⊆ A B\subseteq A B⊆A 是否成立。
B ⊈ A B\nsubseteq A B⊈A:这两个集合有交且没有包含关系,直接输出答案。
B ⊆ A B\subseteq A B⊆A:令 A ← ∁ A B A\gets\complement_AB A←∁AB,相当于把 A A A 中 B B B 的部分挖去。这样做能保证当前存在的所有集合两两无交,且不影响运算结果。
-
c n t = 2 cnt=2 cnt=2
有多个集合与其有交。设当前集合为 B B B,我们可以证明,至少存在一个集合 D D D 满足 D ⊈ B D\nsubseteq B D⊈B 且 D ∩ B ≠ ∅ D\cap B\neq \varnothing D∩B=∅。
我们选取与 B B B 有交的集合中元素个数 k k k 最小的,一定合法,直接输出 B B B 和此集合作为答案。
这样我们就得到了整个的解法过程。接下来的问题就是如何去维护这个 Venn 图。
因为在操作的过程中,我们保证了当前存在的所有集合两两之间无交,所以我们只需要定义一个 t i t_i ti 表示元素 i i i 当前属于哪个集合,如果元素 i i i 当前未被覆盖,则 t i = − 1 t_i=-1 ti=−1。
Code
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define F first
#define S second
using namespace std;
inline ll read()//快读快写
{
ll x=0,f=0;
char ch=getchar();
while(!isdigit(ch))
f|=ch=='-',ch=getchar();
while(isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
template<typename T>inline void write(T x)
{
if(x<0)x=-x,putchar('-');
if(x>9)write(x/10);
putchar(x%10^48);
}
int n,t[1000005];
struct Person//定义集合结构体
{
int sz,id;
vector<int> v;
}a[1000005];
bool cmp(Person &A,Person &B)
{
return A.sz>B.sz;
}
void run()
{
n=read();
for(int i=0;i<n;i++)
t[i]=-1;//初始化
for(int i=0;i<n;i++)
{
a[i].v.clear();
a[i].sz=read();
a[i].id=i+1;
a[i].v.resize(a[i].sz);
for(int j=0;j<a[i].sz;j++)
a[i].v[j]=read()-1;
}
sort(a,a+n,cmp);//将所有集合按元素个数降序排序
unordered_set<int> se;
for(int i=0;i<n;i++)
{
se.clear();
for(auto x:a[i].v)//找与当前集合有交集的所有集合
{
if (t[x]!=-1)
se.insert(t[x]);
}
if ((int)se.size()>1)//cnt>1
{
int cur=*se.begin();
for(auto it=next(se.begin());it!=se.end();it++)
if (a[*it].sz<a[cur].sz)
cur=*it;//找到有交的集合中,元素个数最小的,直接输出答案
puts("YES");
write(a[i].id),putchar(' '),write(a[cur].id),putchar('\n');
return;
}
else if ((int)se.size()==1)//cnt=1
{
int b=*se.begin();
for(auto x:a[i].v)//检查是否有包含关系
if (t[x]!=b)
{
puts("YES");
write(a[i].id),putchar(' '),write(a[b].id),putchar('\n');
return;
}
for(auto x:a[i].v)//有包含关系,挖去当前集合部分
t[x]=i;
}
else//cnt=0
{
for(auto x:a[i].v)
t[x]=i;
}
}
puts("NO");
}
int main()
{
//freopen("discuss.in","r",stdin);
//freopen("discuss.out","w",stdout);
int TT=read();//多测
while(TT--)
run();
return 0;
}
时间复杂度: O ( n log n + ∑ k ) O(n\log n+\sum k) O(nlogn+∑k)
空间复杂度: O ( n + ∑ k + m ) O(n+\sum k+m) O(n+∑k+m)
挺玄乎的思路,不知道大家有没有更好的做法…
希望题解对你有帮助!