洛谷 并查集 or 生成树入门题集

P3367 【模板】并查集

P3367 【模板】并查集

#include<iostream>
#define N 10005
int bb[N];
using namespace std;
inline int get(int x) {
  while(x != bb[x]) {
    x = bb[x]  = bb[bb[x]];
  }
  return x;
}
int main() {
    int n, m, a, b, c;
    cin>>n>>m;
    for(int i = 1; i <= n; ++i) {
       bb[i] = i;
    }
    while(m--) {
      cin>>c>>a>>b;
      a = get(a);
      b = get(b);
      if(c==1) {
        bb[a] = b;
      }else{
        if(a==b) {
          cout<<'Y'<<endl;
        }
        else
          cout<<'N'<<endl;
      }
    }
}

P3366 【模板】最小生成树

P3366 【模板】最小生成树

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 5;
int bb[N];  
int n, m, cnt, ans;
struct node{
  int u, v, w;
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]);
}
void merge(int x, int y, int z) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    ans += z;
    cnt++;
  }
} 
bool cmp(node p, node q) {
  return p.w < q.w;
}
int main() {
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {
    bb[i] = i;
  }
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].u >> a[i].v >> a[i].w;
  }
  sort(a+1, a+m+1, cmp);
  for(int i = 1; i <= m; ++i) {
    merge(find(a[i].u), find(a[i].v), a[i].w);
  }
  if(cnt >= n-1) {
    cout << ans << endl;
  } else puts("orz");
  return 0;
} 

P1551 亲戚

P1551 亲戚
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

输入格式
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。

以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。

接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

输出格式
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

输入输出样例
输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
输出
Yes
Yes
No

#include<iostream>
#include<string.h>
#define N 5005
int bb[N], ll[N];
using namespace std;
int get(int x){
  if(bb[x] == x) return x;
  return bb[x] = get(bb[x]);
}
int main() {
    int n, m, p;
    int a, b;
    cin>>n>>m>>p;
    for(int i = 1; i <= n; ++i) {
      bb[i] = i;
    }
    while(m--) {
      cin>>a>>b;
      a = get(a);
      b = get(b);
      if(a != b) {
        bb[a] = b;
      }
    }
    for(int i = 1; i <= p; ++i) {
      cin>>a>>b;
      a = get(a);
      b = get(b);
      if(a==b) ll[i] = 1;
    }
    for(int i = 1; i <= p; ++i) {
      if(ll[i]) cout<<"Yes"<<endl;
      else cout<<"No"<<endl;
    }
}

P1195 口袋的天空

P1195 口袋的天空
题目背景
小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。

有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。

题目描述
给你云朵的个数NN,再给你MM个关系,表示哪些云朵可以连在一起。

现在小杉要把所有云朵连成KK个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。

输入格式
每组测试数据的

第一行有三个数N,M,K(1 \le N \le 1000,1 \le M \le 10000,1 \le K \le 10)N,M,K(1≤N≤1000,1≤M≤10000,1≤K≤10)

接下来MM行每行三个数X,Y,LX,Y,L,表示XX云和YY云可以通过LL的代价连在一起。(1 \le X,Y \le N,0 \le L<10000)(1≤X,Y≤N,0≤L<10000)

30%30%的数据N \le 100,M \le 1000N≤100,M≤1000

输出格式
对每组数据输出一行,仅有一个整数,表示最小的代价。

如果怎么连都连不出KK个棉花糖,请输出’No Answer’。

输入输出样例
输入
3 1 2
1 2 1
输出
1

思路:板子稍加修改,先按代价从小到大排序,记录合并的次数即可,由于需要把所有的云连成K个棉花糖,所以合并N-K次

#include<iostream>
#include<algorithm> 
using namespace std;
const int N = 1e4 + 5;
int bb[N]; 
int n, m, k;
int num, ans;
struct node{
  int x, y, L;
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]);
}
void merge(int x, int y, int id) {
  x = find(x);
  y = find(y);
  if(y != x) {
    bb[y] = x;
    num++;
    ans += a[id].L;
  }
}
bool cmp(node p, node q) {
  return p.L < q.L;
}
int main() {
  cin >> n >> m >> k;
  for(int i = 1; i <= n; ++i) bb[i] = i; 
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].x >> a[i].y >> a[i].L;
  }
  sort(a+1,a+m+1,cmp);
  for(int i = 1; i <= m; ++i) { 
    merge(a[i].x, a[i].y, i);
    if(num == n - k) {
      cout << ans << endl;
      return 0;
    }
  }
  puts("No Answer");
  cout << ans << endl;
  return 0;
} 

P2330 [SCOI2005]繁忙的都市

P2330 [SCOI2005]繁忙的都市
题目描述
城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:

1.改造的那些道路能够把所有的交叉路口直接或间接的连通起来。 2.在满足要求1的情况下,改造的道路尽量少。 3.在满足要求1、2的情况下,改造的那些道路中分值最大的道路分值尽量小。

任务:作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。

输入格式
第一行有两个整数n,m表示城市有n个交叉路口,m条道路。

接下来m行是对每条道路的描述,u, v, c表示交叉路口u和v之间有道路相连,分值为c。(1≤n≤300,1≤c≤10000,1≤m≤100000)

输出格式
两个整数s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。

输入输出样例
输入
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
输出
3 6

思路裸的最小生成树,其实num就是n-1,因为要让其联通且选择的路尽量少,当时套的板子所以没改。

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
int bb[N];
int n, m;
int num, maax;
struct node{
  int u, v, w; 
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]); 
}
void merge(int x, int y, int id) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    num++;
    maax = max(a[id].w,maax);
  }
}
bool cmp(node p, node q) {  return p.w < q.w; }
int main() {
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {  bb[i] = i; }
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].u >> a[i].v >> a[i].w;
  }
  sort(a+1, a+m+1, cmp);
  for(int i = 1; i <= m; ++i) {
    merge(a[i].u, a[i].v, i);
  }
  cout << num << " " << maax << endl;
  return 0;
}

P1396 营救

P1396 营救
题目背景
“咚咚咚……”“查水表!”原来是查水表来了,现在哪里找这么热心上门的查表员啊!小明感动得热泪盈眶,开起了门……

题目描述
妈妈下班回家,街坊邻居说小明被一群陌生人强行押上了警车!妈妈丰富的经验告诉她小明被带到了 t 区,而自己在 s 区。

该市有 m 条大道连接 n 个区,一条大道将两个区相连接,每个大道有一个拥挤度。小明的妈妈虽然很着急,但是不愿意拥挤的人潮冲乱了她优雅的步伐。所以请你帮她规划一条从 s 至 t 的路线,使得经过道路的拥挤度最大值最小。

输入格式
第一行有四个用空格隔开的 n,m,s,t,其含义见【题目描述】。

接下来 mm 行,每行三个整数 u, v, w,表示有一条大道连接区 u 和区 v,且拥挤度为 w。

两个区之间可能存在多条大道。

输出格式
输出一行一个整数,代表最大的拥挤度。

思路:拥挤度从小到大排序,跑Kruskal,s和t联通时当前权值即结果。

输入输出样例
输入
3 3 1 3
1 2 2
2 3 1
1 3 3
输出
2

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e4 + 5;
int bb[N]; 
int n, m, s, t;
bool flag = 0;
struct node {
  int u, v, w;
}a[N];
int find(int x) {
  return bb[x] == x ? x : bb[x] = find(bb[x]); 
} 
void merge(int x, int y, int id) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    if(find(s) == find(t)) {
      flag = 1;
      cout << a[id].w << endl;
    }
  }
}
bool cmp(node p , node q) { return p.w < q.w; }
int main() {
  cin >> n >> m >> s >> t;
  for(int i = 1; i <= n; ++i) bb[i] = i;
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].u >> a[i].v >> a[i].w;
  }
  sort(a+1,a+m+1,cmp);
  for(int i = 1; i <= m; ++i) {
    merge(a[i].u, a[i].v, i);
    if(flag) return 0;
  }
} 

P1547 [USACO05MAR]Out of Hay S

P1547 [USACO05MAR]Out of Hay S
题目描述
Bessie 计划调查 NN(2 \leq N \leq 2,0002≤N≤2000)个农场的干草情况,它从 11 号农场出发。农场之间总共有 MM(1 \leq M \leq 10^41≤M≤10
4
)条双向道路,所有道路的总长度不超过 10^910
9
。有些农场之间存在着多条道路,所有的农场之间都是连通的。

Bessie 希望计算出该图中最小生成树中的最长边的长度。

输入格式
第一行两个整数 N,M。

接下来 M 行,每行三个用空格隔开的整数 A_i,B_i,L_i,表示 A_i,B_i 之间有一条道路,长度为 L_i​ 。

输出格式
一个整数,表示最小生成树中的最长边的长度。
输入输出样例
输入
3 3
1 2 23
2 3 1000
1 3 43
输出
43

思路参考上题

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e4 + 5;
int bb[N];
int n, m;
int num;
struct node{
  int u, v, w;
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]);
}
void merge(int x, int y) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y ] = x;
    num++;
  }
}
bool cmp(node p, node q){  return p.w < q.w; }
int main() {
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {  bb[i] = i; }
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].u >> a[i].v >> a[i].w;
  }
  sort(a+1, a+m+1, cmp); 
  for(int i = 1; i <= m; ++i) {
    merge(a[i].u, a[i].v);
    if(num == n-1) {//剪枝,最后再输出也可
      cout << a[i].w;
      return 0;
    }
  }
}

P1111 修复公路

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

题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)

输入格式
第1行两个正整数N,M

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

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

输入输出样例
输入
4 4
1 2 6
1 3 4
1 4 5
4 2 3
输出
5
说明/提示
N≤1000,M≤100000
x≤N,y≤N,t≤100000
思路参考上题

#include<iostream>
#include<algorithm> 
using namespace std;
const int N = 1e5 +5;
int bb[N];
int n, m;
int num;
int flag = 1;
struct node{
  int x, y, t;
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]);
}
void merge(int x, int y) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    num++;
  } 
}
bool cmp(node p, node q){
  return p.t < q.t;
}
int main() {
  cin >> n >> m;
  for(int i = 1; i <= n; ++i)  bb[i] = i;
  for(int i = 1; i <= m; ++i) {
    cin >> a[i].x >> a[i].y >> a[i].t;
  }
  sort(a+1, a+m+1, cmp);
  for(int i = 1; i <= m; ++i) {
    merge(a[i].x, a[i].y);
    if(num == n-1) {
      cout << a[i].t;
      return 0;
    }
  }
  puts("-1");
  return 0;
}

P1546 [USACO3.1]最短网络 Agri-Net

P1546 [USACO3.1]最短网络 Agri-Net
题目背景
Farmer John 被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。

题目描述
FJ 已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。

你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过 10^5 。

输入格式
第一行农场的个数 NN(3≤N≤100)。

接下来是一个N×N 的矩阵,表示每个农场之间的距离。理论上,他们是 N 行,每行由 N 个用空格分隔的数组成,实际上,由于每行 80 个字符的限制,因此,某些行会紧接着另一些行。当然,对角线将会是 0,因为不会有线路从第 i 个农场到它本身。

输出格式
只有一个输出,其中包含连接到每个农场的光纤的最小长度。

输入输出样例
输入
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
输出
28
思路可以发现,数据是对称的,所以读入一半数据即可,其余参考上题

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e4 + 5;
int bb[N]; 
int n, t, it;
int num, sum;
struct node{
  int u, v, w;
}a[N];
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]);
}
void merge(int x, int y, int id) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    num++;
    sum += a[id].w;
  }
}
bool cmp(node p, node q) {  return p.w < q.w; }
int main() {
  cin >> n;
  for(int i = 1; i <= n; ++i) {  bb[i] = i; }
  for(int i = 1; i <= n; ++i) {
    for(int j = 1; j <= n; ++j) {
      if(j > i) {
        a[++it].u = i, a[it].v = j;
        cin >> a[it].w;
      } else cin >> t;
    }
  }
  sort(a+1,a+it+1,cmp);
  for(int i = 1; i <= it; ++i) {
    merge(a[i].u, a[i].v, i);
    if(num == n-1) {
      cout << sum << endl;
      return 0;
    }
  }
}

P1536 村村通

P1536 村村通
某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市政府 “村村通工程” 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?

输入格式
输入包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 nn 和道路数目 mm ;随后的 mm 行对应 mm 条道路,每行给出一对用空格隔开的正整数,分别是该条道路直接相连的两个城镇的编号。简单起见,城镇从 11 到 nn 编号。

注意:两个城市间可以有多条道路相通。

输出格式
对于每组数据,对应一行一个整数。表示最少还需要建设的道路数目。

输入输出样例
输入
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
输出
1
0
2
998
思路参考上题 数据有多组,注意初始化

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e3 +5;
int bb[N];
int n, m, u, v;
int num;
int find(int x) {
  return bb[x] == x ? bb[x] : bb[x] = find(bb[x]); 
}
void merge(int x, int y) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    num++;
  }
}
int main() {
  while(cin >> n && n) {
    cin >> m;
    num = 0;
    memset(bb, 0, sizeof(bb));
    for(int i = 1; i <= n; ++i) { bb[i] = i; }
    for(int i = 1;i <= m; ++i) {
      cin >> u >> v;
      merge(u,v);
    }
    cout << n-num-1 << endl;
  }
  return 0;
} 

P2820 局域网

P2820 局域网
题目背景
某个局域网内有n(n<=100)台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用f(i,j)表示i,j之间连接的畅通程度,f(i,j)值越小表示i,j之间连接越通畅,f(i,j)为0表示i,j之间无网线连接。

题目描述
需要解决回路问题,我们将除去一些连线,使得网络中没有回路,并且被除去网线的Σf(i,j)最大,请求出这个最大值。

输入格式
第一行两个正整数n k

接下来的k行每行三个正整数i j m表示i,j两台计算机之间有网线联通,通畅程度为m。

输出格式
一个正整数,Σf(i,j)的最大值
思路逆向思考,既然题目要求除去一些网线,以使网络中无回路,且除去的网线的Σf(i,j)最大,则我们先把记录f(i,j的和sum,然后,按f(i,j)从小到大排序,一直合并,并在合并过程中记录累加f(i,j)ans,直到网络联通输出sum-ans即结果
输入输出样例
输入
5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2
输出
8
说明/提示
f(i,j)<=1000

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e4 + 5;
int bb[N];
int n, k;
int num, sum;
struct node{
	int u, v, w; 
}a[N];
int find(int x) {
  return bb[x] == x ? x : bb[x] = find(bb[x]); 
}
void merge(int x, int y, int id) {
  x = find(x);
  y = find(y);
  if(x != y) {
    bb[y] = x;
    num++;
    sum -= a[id].w;
  }
}
bool cmp(node p, node q) { return p.w < q.w; }
int main(){
	cin >> n >> k;
	for(int i = 1 ;i <= n; ++i) {  bb[i] = i; }
  for(int i = 1 ; i <= k; ++i) {
	  cin >> a[i].u >> a[i].v >> a[i].w; 
    sum += a[i].w;
  }
  sort(a+1, a+k+1, cmp);
  for(int i = 1; i <= k; ++i) {
    merge(a[i].u, a[i].v, i);
    if(num == n - 1) {
      cout << sum << endl;
      return 0;
    }
  }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值