NOI Online2022 t2 讨论 题解

自己现场想的屑解法,望指点。

传送门


Description

这种求集合交、并、包含的问题画一个 Venn 图对思考很有帮助,我现场用这种方法很快切了这道(有和我一样第一道没拿满分第二道拿满了的吗)。

用这种方式,我们来转换一下问题:

给若干个集合,输出两个有交集但没有包含关系的集合。

像这样:


Solution

首先方便起见,我们对所有集合按元素个数 k k k 降序排序。这样的好处是:我们按顺序处理每个集合,之前的集合只有可能包含当前集合,而不可能被当前集合包含,一定程度地简化了问题。

接着,我们每次处理一个集合。我们可以数一下之前处理过的集合中,与它有交的集合个数 c n t cnt cnt。分为 3 种情况:

  1. c n t = 0 cnt=0 cnt=0

    没有集合与其有交,那就先放这不管。

  2. c n t = 1 cnt=1 cnt=1

    有且仅有一个集合与其有交,设它为 A A A,当前集合为 B。那么检查 B ⊆ A B\subseteq A BA 是否成立。

    B ⊈ A B\nsubseteq A BA:这两个集合有交且没有包含关系,直接输出答案。

    B ⊆ A B\subseteq A BA:令 A ← ∁ A B A\gets\complement_AB AAB,相当于把 A A A B B B 的部分挖去。这样做能保证当前存在的所有集合两两无交,且不影响运算结果。

  1. c n t = 2 cnt=2 cnt=2

    有多个集合与其有交。设当前集合为 B B B,我们可以证明,至少存在一个集合 D D D 满足 D ⊈ B D\nsubseteq B DB D ∩ B ≠ ∅ D\cap B\neq \varnothing DB=

    我们选取 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)


挺玄乎的思路,不知道大家有没有更好的做法…

希望题解对你有帮助!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值