并查集的启发式合并

今天的网络赛碰到一道并查集启发式合并的题,但我忘了什么是启发式合并……于是来补补知识了

其实并查集的启发式合并就是加了一个判断,优先选择小的进行合并。看似很吓人的时间复杂度,不过是log级别的,每个点最多只进出log次。

例题2022年的牛客多校

大致题意:有n个论点,每个论点都有多个依赖的论点(如果为0,这个论点是不可被证明的),只有它的前置论点全部正确,它才可以被证明正确。现在可以选择一个论点作为基础论点,假设它是正确的,求最大可以得到的正确的论点的数量。 

思路:如果说a论点可以直接推出b论点,那么贪心的想确定a论点一定是更优的,没必要再从y出发了,把从y连出去的边改成从x连出去。如果只是这样暴力的改边,时间复杂度太高,所以需要加上并查集的启发式合并,把a论点和b论点合并成一个点,每次合并出边数少的那个论点。

#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define endl '\n'
const int N=200010;
typedef pair<int,int>PII;
int n;
set<int>in[N],out[N];
int p[N],sz[N];
int find(int x)
{
	if(x!=p[x]) p[x]=find(p[x]);
	return p[x];
}
void merge(int a,int b)
{
	a=find(a),b=find(b);
	if(a==b) return ;
	vector<PII>v;
	if(out[a].size()<out[b].size()) swap(a,b);
	p[b]=a;sz[a]+=sz[b];
	for(auto t:out[b])
	{
		out[a].insert(t);
		in[t].erase(b);
		in[t].insert(a);
		if(in[t].size()==1)
			v.push_back({a,t});
	}
	for(int i=0;i<v.size();i++)
		merge(v[i].first,v[i].second);

}
int main()
{
	ios;
	int T;
	cin>>T;
	for(int c=1;c<=T;c++)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			in[i].clear();out[i].clear();
			p[i]=i;sz[i]=1;
		}
		for(int i=1;i<=n;i++)
		{
			int k;cin>>k;
			while(k--)
			{
				int x;cin>>x;
				in[i].insert(x);out[x].insert(i);
			}
		}
		for(int i=1;i<=n;i++)
			if(in[i].size()==1) merge(*in[i].begin(),i);
		int res=0;
		for(int i=1;i<=n;i++)
			if(p[i]==i) res=max(res,sz[i]);
		printf("Case #%d: %d\n",c,res);
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值