队内训练3 Codeforces1559D1&D2 启发式合并维护并查集
题目链接link.
题意:给你两个森林。两个森林都有n个节点,第一个森林m1条边,第二个森林m2条边,两个森林公用节点编号,现在问你最多可以连多少条边,使得两个森林都不成环。
这题。乍一眼一看以为是个图论,实际上是数据结构题。思路可以很容易说清楚,但是码量挺难顶的说实话,细节也很多。看到有人随机化过的感觉随机化应该会好写一点。
呐呐,首先我们可以先预处理的时候合并一轮,按照第一个森林划分集合。然后我们的任务就是把这些集合两两合并,合并的条件是,这两个集合中存在一对点,这一对点在第二个森林中不是一个集合的,这样,我们把这两个点连接起来就可以将这两个集合合并了。考虑启发式合并,每次将点数少的往点数多的合并,这样可以保证时间复杂度。考虑自己定义一个结构体来进行合并,结构体存的分别是节点编号和在第二个森林中的并查集的祖先。因为每次合并第二个森林中的点,更新都很麻烦,所以我们可以给第一个森林在合并的时候,每次合并完都去重更新处理。而只有当两个集合的size都为1且他们在第二个森林属于一个连通块时才不存在连边。但是需要注意的是,当前不存在不代表最后不存在,所以最后还要把没有连边的这些集合再遍历一遍。思路就这样,看代码应该更清晰。
当然我的码风可能比上面的文字描述还要不清晰
AC代码: (被喷了八百年的码风)
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
using namespace std;
int n,m1,m2;
int sum=0;
int f[100010];
int f2[100010];
int haha[100010];
int find(int k)
{
if(f[k]==k) return k;
return f[k]=find(f[k]);
}
int find2(int k)
{
if(f2[k]==k) return k;
return f2[k]=find2(f2[k]);
}
struct node
{
int id;
int fa2;
bool friend operator<(node a,node b)
{ return a.fa2<b.fa2; }
}q;
multiset<node>tree1[200010];
pair<int,int> ans[100010];
int final=0;
multiset<node> tool;
int summ,okk;
void yuchuli1()
{
for(int i=1;i<=n;i++)
f[i]=i,f2[i]=i;
for(int i=1;i<=m1;i++)
{
int x,y;
scanf("%d%d",&x,&y);
f[find(y)]=find(x);
}
for(int i=1;i<=m2;i++)
{
int x,y;
scanf("%d%d",&x,&y);
f2[find2(y)]=find2(x);
}
return ;
}
void quchong(int i)
{
tool.clear();
tool.insert( *tree1[i].begin() );
auto it=*tree1[i].begin();
int pre=it.fa2;
for(auto j:tree1[i])
{
if(j.fa2!=pre)
tool.insert(j);
pre=j.fa2;
}
swap(tree1[i],tool);
}
void yuchuli2()
{
for(int i=1;i<=n;i++)
{
int fa=find(i);
if(haha[fa]==0)
{
int oxy=find2(i);
tree1[++sum].insert({i,oxy});
haha[fa]=sum;
}
else
{
int oxy=find2(i);
tree1[ haha[fa] ].insert({i,oxy});
}
}
for(int i=1;i<=sum;i++)
quchong(i);
return ;
}
void update(int i)
{
tool.clear();
for(auto j:tree1[i])
{
int orz=find2(j.id);
tool.insert({j.id,orz});
}
swap(tool,tree1[i]);
}
void solve1(int i)
{
if(okk==0)
{
node fuck=*tree1[i-1].begin();
tree1[sum+(++summ)].insert({fuck.id,find2(fuck.id)});
}
if(i==sum&&okk==0)
{
sum=sum+summ;
okk++;
}
return ;
}
void solve2(int i,node &oxy,node &ozl)
{
int baba=find2(oxy.id);
int mama=oxy.id;
node kkk;
kkk.fa2=oxy.fa2;
if(tree1[i].find(kkk)!=tree1[i].end())
tree1[i].erase( tree1[i].find(kkk) );
kkk.fa2=ozl.fa2;
if(tree1[i].find(kkk)!=tree1[i].end())
tree1[i].erase( tree1[i].find(kkk) );
tree1[i].insert({mama,baba});
for(auto j:tree1[i-1])
{
if(j.fa2!=oxy.fa2&&j.fa2!=ozl.fa2)
{
node kkkk;
kkkk.fa2=j.fa2;
if(tree1[i].find(kkkk)!=tree1[i].end())
continue;
else tree1[i].insert(j);
}
}
return ;
}
int main( )
{
scanf("%d%d%d",&n,&m1,&m2);
yuchuli1();
yuchuli2();
for(int i=2;i<=sum;i++)
{
update(i);
quchong(i);
if(tree1[i-1].size()>=tree1[i].size())
swap(tree1[i-1],tree1[i]);
node oxy=*tree1[i-1].begin();
node ozl=*tree1[i].begin();
if(tree1[i-1].size()==tree1[i].size()&&
tree1[i-1].size()==1&&oxy.fa2==ozl.fa2)
{
solve1(i);
continue;
}
if(oxy.fa2!=ozl.fa2)
{
ans[++final].first=oxy.id;
ans[final].second=ozl.id;
f2[find2(oxy.id)]=find2(ozl.id);
solve2(i,oxy,ozl);
}
else if(oxy.fa2==ozl.fa2)
{
tree1[i].erase(tree1[i].begin());
node otz=*tree1[i].begin();
ans[++final].first=oxy.id;
ans[final].second=otz.id;
f2[find2(oxy.id)]=find2(otz.id);
solve2(i,oxy,otz);
}
if(i==sum&&okk==0)
{
sum=sum+summ;
okk++;
}
}
printf("%d\n",final);
for(int i=1;i<=final;i++)
{
printf("%d %d\n",ans[i].first,ans[i].second);
}
return 0;
}