前几天把洛谷有关并查集几个题目都尝试写了一下,自己提前去了解了一下最短路径(Floyed算法)和强连通分量这一方面的内容便于后续学习。
连通(顾名思义就是把几个点相连,既可以从a到b,也可以从b到a(无向图))
弱连通示例图
下面这图里就有着三个强连通分量:把三个分量各自可以看成一个点,进行度的运算
最短路径(Floyed算法)
在写题的时候总是会遇见这种求最短路径的题,所以提前学习了一下(主要是Floyed)
Floyed就是采用中间点的方式来得到最短路径,可能是一个中转点,也可能有多个中转点,为了得到最优解就会采取不同数目的中转点,这种算法便捷之处在于只要5行就能搞定,但是时间复杂度却达到了O(n^3).
关于并查集的题目有着相同的一些套路
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
对于 30%30% 的数据,N≤10,M≤20。
对于 70%70% 的数据,N≤100,M≤10^3。
对于 100%100% 的数据,1≤N≤10^4,1≤M≤2×10^5,1≤Xi,Yi≤N,Zi∈{1,2}。
思路:这道题用普遍的套路就能得到,把每一次输入的X,Y进行合并操作(对这两个值进行寻找root的操作,若root不相同,就让一个数成为另一个数的父亲),输出完毕后我们对其进行查找,若输入的两个数的父亲相同,就输出Y,否则输出N。
代码:
#include
using namespace std;
int f[10005];
int find(int x) {
if (f[x] == x)return x;
f[x] = find(f[x]);
return f[x];
}
int main() {
int x,y,z,N, M;
cin >> N >> M;
for (int i = 1; i <= N; i++)
f[i] = i;
while (M--) {
cin >> z >> x >> y;
x = find(x);
y = find(y);
if (z == 1) {
if (x != y)f[x] = y;
}
if (z == 2) {
if (x == y)cout << 'Y' << endl;
else cout << 'N' << endl;
}
}
return 0;
}
P1551亲戚问题与模板几乎相似,采用相同的方式很快就能得到结果
P2078 朋友 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
小明在 A 公司工作,小红在 B 公司工作。
这两个公司的员工有一个特点:一个公司的员工都是同性。
A 公司有 N 名员工,其中有 P 对朋友关系。B 公司有 M 名员工,其中有 Q 对朋友关系。朋友的朋友一定还是朋友。
每对朋友关系用两个整数 (Xi,Yi) 组成,表示朋友的编号分别为 Xi,Yi。男人的编号是正数,女人的编号是负数。小明的编号是 11,小红的编号是 −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
对于 30%30% 的数据,N,M≤100,P,Q≤200;
对于 80%80% 的数据,N,M≤4×103,P,Q≤104;
对于 100%100% 的数据,N,M≤104,P,Q≤2×104。
思路:感觉这个题好有意思,当结点出现负数的时候我们要把他变成整数(它的相反数再加一个N值,为了也能使用father数组进行查找和合并),解决了负数的问题后这个题就差不多写完了(和模板即亲戚一样方式的查和并)。
代码:
#include
using namespace std;
int f[100005];
int find(int x) {
if (f[x] == x)return x;
f[x] = find(f[x]);
return f[x];
}
int main() {
int x,y,N, M, P, Q,sum1=0,sum2=0;
cin >> N >> M >> P >> Q;
for (int i = 1; i <= N + M; i++)
f[i] = i;
for (int i = 1; i <= P; i++) {
cin >> x >> y;
if(find(x)!=find(y))
f[find(y)] = find(x);
}
for (int i = 1; i <= Q; i++) {
cin >> x >> y;
x =-x, y =-y;
if(find(x+N)!=find(y+N))
f[find(y+N)] = find(x+N);
}
for (int i = 1; i <= N; i++) {
if (find(i) == find(1))sum1++;
}
for (int i = 1+N; i <= M+N; i++) {
if(find(i)==find(N+1))sum2++;
}
int ans = min(sum1, sum2);
cout << ans << endl;
return 0;
}
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 朵。
一行,表示可以获得的最大价值。
输入
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2
输出
1
思路:对于这道题我使用了01背包来解决,进行合并这一步操作时,不仅要把一个作为另一个的子节点,还需要把两个结点的价钱和价值进行合并,最后使用动态规划来得到最优解
代码:
#include
using namespace std;
int f[10005];
int a[10005],b[10005],arr[10005];
int find(int i) {
if (f[i] == i)return i;
f[i] = find(f[i]);
return f[i];
}
int main() {
int c,d,n, m, w,x,y,u,v;
cin >> n >> m >> w;
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= n; i++) {
scanf("%d %d", &a[i], &b[i]);
}
for (int i = 1; i <= m; i++) {
cin >> u >> v;//查
x = find(u);
y = find(v);
if (x == y)continue;
if (x != y) {//并
f[y] = x;
a[x] += a[y];
b[x] += b[y];
}
}
for (int i = 1; i <= n; i++) {//01背包模板
for (int j = w; j >= a[i]; j--) {
if (f[i] == i) {
arr[j] = max(arr[j], arr[j - a[i]] + b[i]);
}
}
}
cout << arr[w];
return 0;
}