今天的网络赛碰到一道并查集启发式合并的题,但我忘了什么是启发式合并……于是来补补知识了
其实并查集的启发式合并就是加了一个判断,优先选择小的进行合并。看似很吓人的时间复杂度,不过是log级别的,每个点最多只进出log次。
大致题意:有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;
}