每日总结2022.1.6(修复公路、朋友)

并查集有两种让树的高度尽可能小的办法:1.路径压缩2.矮树接高树(不知道该叫什么)

两种方法都是为了避免出现长链状的树。推荐路径压缩。

路径压缩:就是 让爷爷变成父亲  有两种实现方式 递归 和 循环,循环要快一点。

//int find_root(int x,int *parent) {
//    if(parent[x] == x) return x;
//    else return parent[x]=find_root(parent[x],parent);
//}
int find_root(int x,int *parent)//路径压缩
{
	while(parent[x]!=x)
	{
		parent[x]=parent[parent[x]];
		x=parent[x];
	}
	return x;
}

矮树接高树: 让矮的那棵树的根指向高的那棵树的根,连接完后,树的高度为高的那一颗。如果反过来连,就会增加高度。

int connect(int x,int y,int *parent,int *deep)
{
	int x_root=find_root(x,parent);
	int y_root=find_root(y,parent);
	if(x_root==y_root)
	{
		return 0;
	}
	else
	{
		if(deep[x_root]>deep[y_root])
		{
			parent[y_root]=x_root;
		}
		else if(deep[y_root]>deep[x_root])
		{
			parent[x_root]=y_root;
		}
		else
		{
			parent[y_root]=x_root;
			deep[x_root]++;
		}
		return 1;
	}
}

路径压缩让树的深度总是不大于3,但后者虽然也可以避免长链,但还是会出现较高的树。因此,一般采用路径压缩的方式。


P1111 修复公路https://www.luogu.com.cn/problem/P1111

题目背景

AA地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。

题目描述

给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修

完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)

输入格式

第11行两个正整数N,M

下面M行,每行3个正整数x, y, 告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。

输出格式

如果全部公路修复完毕仍然存在两个村庄无法通车,则输出-1,否则输出最早什么时候任意两个村庄能够通车。

输入输出样例

输入 #1复制

4 4
1 2 6
1 3 4
1 4 5
4 2 3

输出 #1复制

5

说明/提示

N≤1000,M≤100000

x≤N,y≤N,t≤100000

思路:按照时间顺序去修路(合并集合),可以通过结构体来表示一天公路,包含x,y,t。然后排序,从t小的可是合并集合。

代码实现

#include<bits/stdc++.h>
using namespace std;
int N;
struct link{
	int x;
	int y;
	int t;
};
//int find_root(int x,int *parent) {
//    if(parent[x] == x) return x;
//    else return parent[x]=find_root(parent[x],parent);
//}
int find_root(int x,int *parent)//路径压缩
{
	while(parent[x]!=x)
	{
		parent[x]=parent[parent[x]];
		x=parent[x];
	}
	return x;
}

int connect(int x,int y,int *parent)
{
	int x_root=find_root(x,parent);
	int y_root=find_root(y,parent);
	if(x_root==y_root)
	{
		return 0;
	}
	else
	{
//		if(deep[x_root]>deep[y_root])
//		{
//			parent[y_root]=x_root;
//		}
//		else if(deep[y_root]>deep[x_root])
//		{
//			parent[x_root]=y_root;
//		}
//		else
//		{
//			parent[x_root]=y_root;
//			deep[x_root]++;
//		}
		parent[x_root]=y_root;
		N--;
		return 1;
	}
}
bool up(link a,link b)
{
	return a.t<b.t;
}
int main()
{
	int M;
	int parent[1000+5]={0};
//	int deep[1000+5]={0};
	cin>>N>>M;
	for(int i=0;i<=N;i++)
	{
		parent[i]=i;
	}
	link way[100000];
	for(int i=0;i<M;i++)
	{
		cin>>way[i].x>>way[i].y>>way[i].t;
	}
//	for(int i=0;i<M;i++)
//	{
//		for(int j=i+1;j<M;j++)
//		{
//			if(way[j].t<way[i].t)
//			{
//				link temp=way[i];
//				way[i]=way[j];
//				way[j]=temp;
//			}
//		}
//	}	
	sort(way,way+M,up);
//	int flag=1;
	for(int i=0;i<M;i++)
	{
		connect(way[i].x,way[i].y,parent);
//		flag=1;
//		for(int j=1;j<N;j++)
//		{
//			if(find_root(j,parent)!=find_root(j+1,parent))
//			{
//				flag=0;
//				break;
//			}
//		}
//		if(flag) 
//		{
//			cout<<way[i].t;
//			break;
//		}
		if(N==1)
		{
			cout<<way[i].t;
			return 0;
		}
	}
	cout<<-1;
	return 0;
//	if(flag==0)
//	{
//		printf("-1\n");
//	}
	return 0;
}

注意,数据比较多,要用快排和压缩路径


P2078 朋友https://www.luogu.com.cn/problem/P2078

题目背景

小明在 A 公司工作,小红在 B 公司工作。

题目描述

这两个公司的员工有一个特点:一个公司的员工都是同性。

A 公司有 N 名员工,其中有 P 对朋友关系。B 公司有 M 名员工,其中有 Q 对朋友关系。朋友的朋友一定还是朋友。

每对朋友关系用两个整数 (Xi​,Yi​) 组成,表示朋友的编号分别为Xi​,Yi​。男人的编号是正数,女人的编号是负数。小明的编号是 1,小红的编号是 -1。

大家都知道,小明和小红是朋友,那么,请你写一个程序求出两公司之间,通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。

输入格式

输入的第一行,包含 4 个空格隔开的正整数 N,M,P,Q。

之后 P 行,每行两个正整数 Xi​,Yi​。

之后 Q 行,每行两个负整数Xi​,Yi​。

输出格式

输出一行一个正整数,表示通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。

输入输出样例

输入 #1复制

4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3

输出 #1复制

2

说明/提示

对于30% 的数据,N,M≤100,P,Q≤200;

对于80% 的数据,N,M≤4×103,P,Q≤104;

对于 100% 的数据,N,M≤104,P,Q≤2×104。

思路:两个并查集,一个负责A公司一个负责B公司。数组下标不能为赋,所以要把x和y变为正数。统计每个公司和小红或者小明有关系的人数,输出少的那一方,记得他们自己。

代码实现

#include<bits/stdc++.h>
using namespace std;

int find_root(int x,int *parent)
{
	while(x!=parent[x])
	{
		parent[x]=parent[parent[x]];
		x=parent[x];
	}
	return x;
}

int connect(int x,int y,int *parent)
{
	int x_root=find_root(x,parent);
	int y_root=find_root(y,parent);
	if(x_root==y_root)
	{
		return 0;
	}
	else
	{
		parent[y_root]=x_root;
		return 1;
	}
}

void init(int *parent,int N)
{
	for(int i=1;i<=N;i++)
	{
		parent[i]=i;
	}
}

int main()
{
	int N,M,P,Q;
	cin>>N>>M>>P>>Q;
	int parentA[100000+5];
	int parentB[100000+5];
	init(parentA,N);
	init(parentB,M);
	int x,y;
	for(int i=1;i<=P;i++)
	{
		cin>>x>>y;
		connect(x,y,parentA);
	}
	for(int i=1;i<=Q;i++)
	{
		cin>>x>>y;
		x=-x;y=-y;
		connect(x,y,parentB);
	}
	int A=0,B=0;
	int a=find_root(1,parentA);
	int b=find_root(1,parentB);
	for(int i=2;i<=N;i++)
	{
		if(find_root(i,parentA)==a)
		{
			A++;
		}
	}
	for(int i=2;i<=M;i++)
	{
		if(find_root(i,parentB)==b)
		{
			B++;
		}
	}
	int ans=A>B?B:A;
	printf("%d",ans+1);	
	return 0;
}

注意数组开大一点,两倍N或M,不能仅仅是10000+5这么大,不然会re。我也不知道为什么,求解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值