目录
1.【模板】并查集
2.亲戚
3.搭配购买
4.朋友
5.修复公路
P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。 接下来 M 行,每行包含三个整数 Zi,Xi,Yi 。 当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。 当 Zi=2 时,输出 Xi 与 Yi 是否在同一集合内,是的输出 Y ;否则输出 N 。
输出格式
对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
输入输出样例
输入 #1
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4
输出 #1
N Y N Y
这道题直接套用Find函数和Union函数。
Find函数:
int Find(int x)
{
if(pre[x] == x) return x;
return pre[x] = Find(pre[x]);
}
Union函数:
void Union(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx==fy) return;
pre[fx]=fy;
}
完整AC代码:
#include
using namespace std;
int pre[10001];
int Find(int x)
{
if(pre[x] == x) return x;
return pre[x] = Find(pre[x]);
}
void Union(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx==fy) return;
pre[fx]=fy;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
pre[i]=i;
}
while(m--){
int z,x,y;
cin>>z>>x>>y;
if(z==1) Union(x,y);
else
{
if(Find(x)==Find(y)) cout<<"Y"<
P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数n,m,p(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi,Mj,1≤Mi, Mj≤n,表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
p 行,每行一个 Yes
或 No
。表示第 i 个询问的答案为“具有”或“不具有”亲戚关系。
输入输出样例
输入 #1
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6
输出 #1
Yes Yes No
这道题其实和第一题是一样的,区别就是第一题是一边并一边查,这一题是先并完再查,将上一题代码的输入输出改一下就可以了。
#include
using namespace std;
int pre[10010];
int Find(int x)
{
if(pre[x] == x) return x;
return pre[x] = Find(pre[x]);
}
void Union(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx==fy) return ;
pre[fx]=fy;
}
int main()
{
int n,m,t;
cin>>n>>m>>t;
for(int i=1;i<=n;i++){
pre[i]=i;
}
while(m--){
int x,y;
cin>>x>>y;
Union(x,y);
}
while(t--){
int x,y;
cin>>x>>y;
if(Find(x)==Find(y))
cout<<"Yes"<
P1455 搭配购买 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 n 朵云,云朵已经被老板编号为1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。
输出格式
第一行输入三个整数,n,m,w,表示有 n 朵云,m 个搭配和你现有的钱的数目。
第二行至 n+1 行,每行有两个整数, ci,di,表示第 i 朵云的价钱和价值。
第 n+2 至n+1+m 行 ,每行有两个整数ui,vi。表示买第 ui 朵云就必须买第 vi 朵云,同理,如果买第 vi 朵就必须买第 ui 朵。
输出格式
一行,表示可以获得的最大价值。
输入输出样例
输入 #1
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2
输出 #1
1
这道题大家看到最大价值是否想起了一个老朋友,“01背包问题”,所以这道题是需要动态规划和并查集搭配解决,原理是找到联通的一块集合,因为买了其中一个,那么就必须买另外的,那么我们不妨将集合中节点的所有价钱和价值都加到根节点去,然后将根节点的价钱和价值放到一个新开的数组里面,再利用dp数组在新开的数组里面找最大价值。
下面看完整AC代码:
#include
using namespace std;
int pre[10010];
struct sub{
int v,w;
}a[10010];
int v[10001],w[10001],v1=0,dp[10001];
int Find(int x)
{
if(pre[x] == x) return x;
return pre[x] = Find(pre[x]);
}
void Union(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx==fy) return ;
a[fy].v+=a[fx].v;//将接入的节点的价钱和价值加到根节点(祖先)上
a[fy].w+=a[fx].w;
pre[fx]=fy;
}
int main()
{
int n,t,m;
cin>>n>>t>>m;
for(int i=1;i<=n;i++)
cin>>a[i].v>>a[i].w,pre[i]=i;//输出加初始化
while(t--)
{
int x,y;
cin>>x>>y;
Union(x,y);
}
for(int i=1;i<=n;i++){
if(pre[i]==i)//找到每个集合的根节点
{
v[++v1]=a[i].v;//记录
w[v1]=a[i].w;
}
}
for(int i=1;i<=v1;i++){
for(int j=m;j>=v[i];j--){//动态规划出最大价值
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<
P2078 朋友 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
小明在 A 公司工作,小红在 B 公司工作。
题目描述
这两个公司的员工有一个特点:一个公司的员工都是同性。
A 公司有 N 名员工,其中有 P 对朋友关系。B 公司有 M 名员工,其中有 Q 对朋友关系。朋友的朋友一定还是朋友。
每对朋友关系用两个整数 (Xi,Yi) 组成,表示朋友的编号分别为 Xi,Yi。男人的编号是正数,女人的编号是负数。小明的编号是 1,小红的编号是 −1。
大家都知道,小明和小红是朋友,那么,请你写一个程序求出两公司之间,通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。
输出格式
输入的第一行,包含 44 个空格隔开的正整数 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
我们将小明(1)和小红(-1)分别作为两个集合的根节点,我们找到两个集合的元素的最小值,就是可以搭配出的情侣。
下面看完整AC代码:
#include
using namespace std;
int pre1[20010],pre2[20010],ans1=0,ans2=0;
int Findm(int x)
{
if(pre1[x] == x) return x;
return pre1[x] = Findm(pre1[x]);
}
int Findw(int x)
{
if(pre2[x]==x) return x;
return pre2[x]=Findw(pre2[x]);
}
int main()
{
int n,m,p,q;
cin>>n>>m>>p>>q;
for(int i=1;i<=n;i++)
pre1[i]=i;//利用两个数组来代表两个集合
for(int i=1;i<=m;i++)
pre2[i]=i;
while(p--)
{
int x,y;
cin>>x>>y;
if(Findm(x)!=Findm(y))
{
pre1[Findm(x)]=Findm(y);//加入集合
}
}
while(q--)
{
int x,y;
cin>>x>>y;
x=-x,y=-y;
if(Findw(x)!=Findw(y))
{
pre2[Findw(x)]=Findw(y);
}
}
for(int i=1;i<=n;i++)
{
if(Findm(pre1[i])==Findm(pre1[1])) ans1++;//如果祖先是第一个节点,也就是小明,那么在同一个集合内
}
for(int i=1;i<=m;i++){
if(Findw(i)==Findw(pre2[1])) ans2++;
}
cout<
P1111 修复公路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
A 地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出 A 地区的村庄数 N,和公路数 M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)。
输入格式
第 1 行两个正整数 N,M。
下面 M 行,每行 3 个正整数 x,y,t,告诉你这条公路连着 x,y 两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出 −1−1,否则输出最早什么时候任意两个村庄能够通车。
输入输出样例
输入 #1
4 4 1 2 6 1 3 4 1 4 5 4 2 3
输出 #1
5
由于公路是同时开始修的,那么我们只需要使用到贪心,按照时间从小到大排序,,只要在某个时间发现,此时的村庄都在一个集合,那么此时的时间就是最小时间。
下面是完整AC代码:
#include
using namespace std;
int pre[100100];
struct node{
int x,y,t;
}a[100010];
bool cmp(node &x,node &y)
{
return x.t>n>>m;
for(int i=1;i<=n;i++){
pre[i]=i;
}
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y>>a[i].t;
}
sort(a+1,a+1+m,cmp);//按照时间从小到大排序
int i;
for(i=1;i<=m;i++){
if(Find(a[i].x)!=Find(a[i].y))//必须不在同一个集合上
{
Union(a[i].x,a[i].y);
tot++;//有加入集合的村庄,总数加1
}
if(tot==n)
break;
}
if(tot==n) cout<