并查集
洛谷P1111
题目背景
A地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入格式
第1行两个正整数N,M
下面M行,每行3个正整数x, y, t告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出-1,否则输出最早什么时候任意两个村庄能够通车。
算法思想
并查集维护联通集,用贪心策略,初始n个点就是n个联通集,将边从早到晚排序,逐步判断各边的两个点是否联通,如果未联通,则插入这条边,并减少联通集个数,并进行特判。
#include <bits/stdc++.h>
using namespace std;
struct node{
int x, y, z;
};
const int N = 1e6 + 10;
node e[N];
int f[N];
int find(int a)
{
if (f[a] != a) f[a] = find(f[a]);
return f[a];
}
bool cmp(node p, node q)
{
return p.z < q.z;
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> e[i].x >> e[i].y >> e[i].z;
for (int i = 1; i <= n; i++)
f[i] = i;
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i++)
{
int l = find(e[i].x);
int r = find(e[i].y);
if (l != r)
{
f[r] = l;
n--;
if (n == 1){
cout << e[i].z << endl;
return 0;
}
}
}
cout << -1 << endl;
return 0;
}
洛谷P2814
题目背景
现代的人对于本家族血统越来越感兴趣。
题目描述
给出充足的父子关系,请你编写程序找到某个人的最早的祖先。
输入格式
输入由多行组成,首先是一系列有关父子关系的描述,其中每一组父子关系中父亲只有一行,儿子可能有若干行,用 #name
的形式描写一组父子关系中的父亲的名字,用 +name
的形式描写一组父子关系中的儿子的名字;接下来用 ?name
的形式表示要求该人的最早的祖先;最后用单独的一个 $
表示文件结束。
输出格式
按照输入文件的要求顺序,求出每一个要找祖先的人的祖先,格式为:本人的名字 ++ 一个空格 ++ 祖先的名字 ++ 回车。
输入输出样例
输入 #1
#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$
输出 #1
Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur
算法思想
string到string的并查集,用map来实现。
代码实现
/*
string到string的并查集
如果是父亲,如果没爹,初始化根节点,并记录父亲姓名。
如果是儿子,要找到上一个记录的姓名的祖宗。
*/
#include <bits/stdc++.h>
using namespace std;
map<string, string> f;
string find(string a)
{
if (f[a] != a) f[a] = find(f[a]);
return f[a];
}
int main()
{
char c;
string sp, s;
cin >> c;
while(c != '$')
{
cin >> s;
if (c == '#'){
sp = s;
if (f[s] == "")
f[s] = s;
}
else if (c == '+')
{
f[s] = find(sp);
}
else{
cout << s << ' ' << find(f[s]) << endl;
}
cin >> c;//记得输入c 控制循环
}
return 0;
}
洛谷P1955
题目描述
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 x_1,x_2,x_3,\cdotsx1,x2,x3,⋯ 代表程序中出现的变量,给定 nn 个形如 x_i=x_jx**i=x**j 或 x_i\neq x_jx**i\=x**j 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x_1=x_2,x_2=x_3,x_3=x_4,x_4\neq x_1x1=x2,x2=x3,x3=x4,x4\=x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
输入格式
输入的第一行包含一个正整数 tt,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第一行包含一个正整数 nn,表示该问题中需要被满足的约束条件个数。接下来 nn 行,每行包括三个整数 i,j,ei,j,e,描述一个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e=1e=1,则该约束条件为 x_i=x_jx**i=x**j。若e=0e=0,则该约束条件为 x_i\neq x_jx**i\=x**j。
输出格式
输出包括 t 行。
输出文件的第 kk 行输出一个字符串 YES
或者 NO
(字母全部大写),YES
表示输入中的第 kk 个问题判定为可以被满足,NO
表示不可被满足。
算法思想
对于等式可以看做两个元素为同一个集合,用并查集维护,先排序,相等加入并查集,不等式做判断。注意本题需要离散化。离散化三部曲:排序,去重,索引。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
struct node{
int x, y, e;
};
node a[N];
int f[N];
int p[N];
bool cmp(node P, node Q)
{
return P.e > Q.e;
}
int find(int a)
{
if (f[a] != a) f[a] = find(f[a]);
return f[a];
}
int main()
{
int t;
cin >> t;
while(t--)
{
memset(a, 0, sizeof(a));
memset(f, 0, sizeof(f));
memset(p, 0, sizeof(p));
int n;
cin >> n;
for (int i = 1; i <= n * 2; i++)
f[i] = i;
int tnt = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i].x >> a[i].y >> a[i].e;
p[++tnt] = a[i].x;
p[++tnt] = a[i].y;
}
sort(p + 1, p + tnt + 1);
int bd = unique(p + 1, p + tnt + 1) - (p + 1) + 1; // 相对位置 和数组从1开始
for (int i = 1; i <= n; i++)
{
a[i].x = lower_bound(p + 1, p + bd, a[i].x) - p - 1;
a[i].y = lower_bound(p + 1, p + bd, a[i].y) - p - 1;
}
sort(a + 1, a + n + 1, cmp);
int flag = 0;
for (int i = 1; i <= n; i++)
{
int rx = find(a[i].x);
int ry = find(a[i].y);
if (a[i].e == 1)
{
f[rx] = ry;
}
else if(rx == ry)
{
flag = 1;
break;
}
}
if (flag) puts("NO");
else puts("YES");
}
return 0;
}
关押罪犯 洛谷1525
题目描述
S 城现有两座监狱,一共关押着 NN 名罪犯,编号分别为 1-N1−N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 cc 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 cc 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了NN 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
每行中两个数之间用一个空格隔开。第一行为两个正整数 N,MN,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 MM 行每行为三个正整数 a_j,b_j,c_ja**j,b**j,c**j,表示 a_ja**j 号和 b_jb**j 号罪犯之间存在仇恨,其怨气值为 c_jc**j。数据保证 1<a_j\leq b_j\leq N, 0 < c_j\leq 10^91<a**j≤b**j≤N,0<c**j≤109,且每对罪犯组合只出现一次。
输出格式
共 11 行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0
。
输入输出样例
输入 #1复制
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
输出 #1复制
3512
算法思想
本体属于关系并查集,如果两个节点属于同一颗树,通过路径来判断两个节点的关系,如果不属于同一颗树,则需要建立关系,并查集需要维护每个点到根节点的距离。本体关键点:如何维护路径长度,如何通过距离判断关系,如何建立关系。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int f[N], d[N];
struct node{
int x, y, w;
}e[N];
bool cmp(node P, node Q)
{
return P.w > Q.w;
}
int find(int a){
if (f[a] != a)
{
int fa = f[a]; //用fa保存父节点,不然回溯过来会丢失;
f[a] = find(f[a]);
d[a] = (d[a] + d[fa])% 2;
}
return f[a];
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= m; i++)
{
cin >> e[i].x >> e[i].y >> e[i].w;
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i++)
{
int rx = find(e[i].x);
int ry = find(e[i].y);
if (rx == ry) //有关系
{
if ((d[e[i].x] - d[e[i].y] + 2) % 2 == 0){ //在不同监狱
cout << e[i].w << endl;
return 0;
}
}
else { //没有关系
f[rx] = ry;
d[rx] = (1 + d[e[i].y] - d[e[i].x]) % 2;
}
}
cout << 0 << endl;
return 0;
}
Liars and truth tellers
算法思想
如果是L关系,无论第一头牛是真话牛还是假话牛,两者状态都相反,同理T关系两者状态相同。初始化并查集,查询xi和yi是否有关系(在同一颗树)中。
在同一颗树,是否矛盾,否则建立关系。
代码实现
// L 路径为1 ,T为0.
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int f[N], d[N];
int find(int a)
{
int fa = f[a];
if (f[a] != a){
f[a] = find(f[a]);
d[a] = (d[a] + d[fa]) % 2;
}
return f[a];
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= m; i++)
{
int x, y;
char c;
cin >> x >> y >> c;
int rx = find(x);
int ry = find(y);
if (rx == ry)
{
char flag = 'T';
if ((d[x] - d[y] + 2) % 2) flag = 'F';
else flag = 'T';
if (c != flag){
cout << i - 1 << endl;
return 0;
}
}
else{
f[rx] = ry;
if (c == 'T'){
d[rx] = (d[y] - d[x] + 2) % 2;
}
else d[rx] = (d[y] - d[x] + 1) % 2;
}
}
return 0;
}
食物链 acwings240
动物王国中有三类动物 A,B,CA,B,C,这三类动物的食物链构成了有趣的环形。
AA 吃 BB,BB 吃 CC,CC 吃 AA。
现有 NN 个动物,以 1∼N1∼N 编号。
每个动物都是 A,B,CA,B,C 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 NN 个动物所构成的食物链关系进行描述:
第一种说法是 1 X Y
,表示 XX 和 YY 是同类。
第二种说法是 2 X Y
,表示 XX 吃 YY。
此人对 NN 个动物,用上述两种说法,一句接一句地说出 KK 句话,这 KK 句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话;
- 当前的话中 XX 或 YY 比 NN 大,就是假话;
- 当前的话表示 XX 吃 XX,就是假话。
你的任务是根据给定的 NN 和 KK 句话,输出假话的总数。
输入格式
第一行是两个整数 NN 和 KK,以一个空格分隔。
以下 KK 行每行是三个正整数 D,X,YD,X,Y,两数之间用一个空格隔开,其中 DD 表示说法的种类。
若 D=1D=1,则表示 XX 和 YY 是同类。
若 D=2D=2,则表示 XX 吃 YY。
输出格式
只有一个整数,表示假话的数目。
数据范围
1≤N≤500001≤N≤50000,
0≤K≤1000000≤K≤100000
输入样例:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例:
3
算法思想
和奶牛问题一样,本质是用并查集维护关系,可以通过节点到根节点的距离将所有动物分为3类,当(d[x] - d[y] + 3) % 3 等于0、1、2时候,分别表示同类、吃、被吃的关系,注意,所有涉及到路径运算时候,都要对3取余,保证所有的边权都是在规定范围内。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 10;
int f[N], d[N];
int find(int a)
{
int fa = f[a];
if (f[a] != a){
f[a] = find(f[a]);
d[a] = (d[a] + d[fa]) % 3; // 要对3取余
}
return f[a];
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
f[i] = i;
int res = 0;
while(m--)
{
int k, x, y;
cin >> k >> x >> y;
if ( x > n || y > n )
{
res ++ ;
continue;
}
if (k == 2 && x == y)
{
res ++;
continue;
}
int rx = find(x);
int ry = find(y);
if (rx == ry){
int r = (d[x] - d[y] + 3) % 3; // r = 0 同类 r = 1 x 吃 y r = 2 y 吃 x
if (k - 1 != r) res ++ ;
//
}
else{
f[rx] = ry;
d[rx] = (d[y] - d[x] + 2 + k) % 3;
}
}
cout << res << endl;
return 0;
}