队内训练3 Codeforces1559D1&D2 启发式合并维护并查集

队内训练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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值