在近几年的国赛(即NOI)中, n ≤ 1 0 8 , 1 0 9 n\leq 10^8,10^9 n≤108,109等大数据频繁出现,所以,这时我们就要引入一种新的数据结构——并查集
并查集( D i s j o i n t − S e t Disjoint-Set Disjoint−Set o r or or U n i o n − F i n d Union-Find Union−Find S e t Set Set)是一种表示不相交集合的数据结构,用于处理不相交集合的合并与查询问题。在不相交集合中,每个集合通过代表来区分,代表是集合中的某个成员,能够起到唯一标识该集合的作用。一般来说,选择哪一个元素作为代表是无关紧要的,关键是在进行查找操作时,得到的答案是一致的(通常把并查集数据结构构造成树形结构,根节点即为代表)。
在不相交集合上,需要经常进行如下操作:
F i n d S e t ( x ) FindSet(x) FindSet(x):查找元素 x x x属于哪个集合,如果 x x x 属于某一集合,则返回该集合的代表。
U n i o n S e t ( x , y ) UnionSet(x,y) UnionSet(x,y):如果元素 x x x 和元素 y y y 分别属于不同的集合,则将两个集合合并,否则不做操作。
并查集的实现方法是使用有根树来表示集合——树中的每个结点都表示集合的一个元素,每棵树表示一个集合,每棵树的根结点作为该集合的代表。
我相信你跟我一样,根本看不懂,那就让我来举个例子
有一所中学内有 28 28 28个班,比如 1 1 1班,我们不可能选择一个没有代表性的人来代表整个班,所以这时,我们用班主任 h e a d 1 head_1 head1代表整个班,再比如 2 2 2班,同样的,班主任 h e a d 2 head_2 head2也可以代表整个班
查询操作是递归查询,在查询某个结点在哪一个集合中时,需沿着其父结点,递归向上,因所属集合代表指向的仍然是其本身,所以可以以 f a t h e r [ x ] = x father[x] = x father[x]=x作为递归查询出口。
int FindSet(int x) {
if (father[x] == x) return x;
else return FindSet(father[x]);
}
F i n d S e t FindSet FindSet :如果知道一班有一个人叫 s t u 1 stu_1 stu1,那么我们可以利用他的爸爸们(比如: g r o u p 1 , m o n i t o r 1 group_1,monitor_1 group1,monitor1)来找到他的祖先— h e a d 1 head_1 head1(注:祖先就是他爸爸的爸爸(此处省略 n n n个爸爸 1 ≤ n 1 \leq n 1≤n))
在进行集合的合并时,只需将两个集合的代表进行连接即可,即一个代表作为另一个代表的父结点。
void UnionSet(int x, int y) {
father[FindSet(x)] = FindSet(y);
}
U n i o n S e t ( x , y ) UnionSet(x,y) UnionSet(x,y):如果想合并1班和2班只需要让班主任 h e a d 2 head_2 head2成为班主任 h e a d 1 head_1 head1的爸爸或者让班主任 h e a d 2 head_2 head2成为班主任 h e a d 1 head_1 head1的儿子
普通的查找实在是太慢,所以,我们得好好想想(用脑袋想不出来时,可以用脚趾敲敲代码,利用 m o n k e y monkey monkey定理打出正确代码 )
发生原因:在合并过程中,可能会形成一条长长的链,随着链越来越长,我们想要从底部找到根结点会变得越来越难。
方法:路径压缩:对于一个集合中的结点,只需要关心它的根结点是谁,不必知道各结点之间的关系(对树的形态不关心),希望每个元素到根结点的路径尽可能短,最好只需要一步。把一条链变成一棵又矮又胖的树,极大地提高了查询效率。
int FindSet(int x) {
if (father[x] == x) return x;
else return father[x] = FindSet(father[x]);
}
这就好比班主任 h e a d 2 head_2 head2有一个儿子 c h i l d 1 child_1 child1,而 c h i l d 1 child_1 child1又有一个儿子 c c h i l d 1 cchild_1 cchild1,现在,直接让 h e a d 2 head_2 head2的孙子 c c h i l d 1 cchild_1 cchild1变成他的儿子 c h i l d 2 child_2 child2
(辈分乱了,大逆不道啊.
对于一些毒瘤数据,只有一个优化远远不够,所以,还得用上按秩合并(启发式合并)
//初始化
void MakeSet(int n) {
for (int i = 1; i <= n; i++)
father[i] = i, rank[i] = 1;
}
void UnionSet(int x, int y) {
int a = FindSet(x), b = FindSet(y);
if (a == b) return;
if (rank[a] <= rank[b]) father[a] = b;
else father[b] = a;
if (rank[a] == rank[b]) rank[b]++;
}
想要合并一班和二班,我们就比较哪边班主任的儿子多(注:此处的儿子指该班上的人),就让另一个班的班主任 h e a d 1 head_1 head1变成 h e a d 2 head_2 head2的儿子
在有了路径压缩后,一条链变成了一棵又矮又胖的树,失去了他原来父亲与儿子间的关系.
那么,当我们遇到银河英雄传说
与箱子
时,我们就会悔恨当初为什么要合并,但是还是有补救的办法———我们需要引入2个数组 d e e p i deep_i deepi来表示从 r o o t root root到第 i i i个点的距离, s i z e i size_i sizei来表示从链底到第 i i i个点的点的数量
一般的并查集,维护的是具有连通性、传递性的关系,例如亲戚的亲戚是亲戚。但有时候要维护另一种关系:敌人的敌人是朋友(如
食物链
和团伙
),种类并查集就是为了解决这个问题。
例如下图所示, ( 1 , 2 ) (1, 2) (1,2)是敌人, ( 2 , 4 ) (2, 4) (2,4)是敌人,则 ( 1 , 4 ) (1, 4) (1,4)就是朋友。
正所谓大量的练习才能促进进步。
P1551 亲戚
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定: x x x 和 y y y 是亲戚, y y y 和 z z z 是亲戚,那么 x x x 和 z z z 也是亲戚。如果 x x x, y y y 是亲戚,那么 x x x 的亲戚都是 y y y 的亲戚, y y y 的亲戚也都是 x x x 的亲戚。
第一行:三个整数 n , m , p n,m,p n,m,p,( n , m , p ≤ 5000 n,m,p \le 5000 n,m,p≤5000),分别表示有 n n n 个人, m m m 个亲戚关系,询问 p p p 对亲戚关系。
以下 m m m 行:每行两个数 M i M_i Mi, M j M_j Mj, 1 ≤ M i , M j ≤ N 1 \le M_i,~M_j\le N 1≤Mi, Mj≤N,表示 M i M_i Mi 和 M j M_j Mj 具有亲戚关系。
接下来 p p p 行:每行两个数 P i , P j P_i,P_j Pi,Pj,询问 P i P_i Pi 和 P j P_j Pj 是否具有亲戚关系。
p p p 行,每行一个 Yes
或 No
。表示第 i i i 个询问的答案为“具有”或“不具有”亲戚关系。
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
Yes
Yes
No
这是一道模板题 (
这显而易见,解法见正常版代码
#include
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
static char buf[100000], *pa = buf, *pd = buf;
#define gc pa == pd && (pd = (pa = buf) + fread(buf, 1, 100000, stdin), pa == pd) ? EOF : *pa++
inline long long read() {
register long long x(0);
register char c(gc);
while (c < '0' || c > '9') c = gc;
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = gc;
return x;
}
inline void write(long long x) {
if (x == 0) {
putchar('0');
return;
}
long long len = 0, k1 = x, c[10005];
if (k1 < 0)
k1 = -k1, putchar('-');
while (k1) c[len++] = k1 % 10 + '0', k1 /= 10;
while (len--) putchar(c[len]);
putchar('\n');
}
int n, m, fa[20005], k, rank[20005];
inline int find(int x) {
if (fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
}
int main() {
n = read(), m = read(), k = read();
for (int i = 1; i <= n; i++) fa[i] = i, rank[i] = 1;
for (int i = 1; i <= m; i++) {
int x, y;
x = read(), y = read();
int fx = find(x);
int fy = find(y);
if (rank[fx] <= rank[fy]) fa[fx] = fy;
else fa[fy] = fx;
if (rank[fx] == rank[fy]) rank[fy]++;
}
for (int i = 1; i <= k; i++) {
int x, y;
x = read(), y = read();
if (find(x) != find(y)) {
putchar('N');
putchar('o');
putchar('\n');
} else {
putchar('Y');
putchar('e');
putchar('s');
putchar('\n');
}
}
return 0;
}
#include
using namespace std;
int n, m, fa[20005], k;
int find(int x) {
if(fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
void unionset(int x, int y){
int fx = find(x), fy = find(y);
if(fx != fy) fa[fx] = fy;
}
int main() {
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) {
int x, y;
scanf("%d %d", &x, &y);
unionset(x, y); //建并查集
}
for(int i = 1; i <= k; i ++){
int x, y;
scanf("%d %d", &x, &y);
if(find(x) != find(y)) printf("No\n"); // 查询
else printf("Yes\n");
}
return 0;
}
某个地区有 n ( n ≤ 1000 ) n(n≤1000) n(n≤1000)个犯罪团伙,当地警方按照他们的危险程度由高到低给他们编号为 1 , 2 , … , n 1,2,\ldots,n 1,2,…,n,他们有些团伙之间有直接联系,但是任意两个团伙都可以通过直接或间接的方式联系,这样这里就形成了一个庞大的犯罪集团,犯罪集团的危险程度由集团内的犯罪团伙数量唯一确定,而与单个犯罪团伙的危险程度无关(该犯罪集团的危险程度为 n n n)。现在当地警方希望花尽量少的时间(即打击掉尽量少的团伙),使得庞大的犯罪集团分离成若干个较小的集团,并且他们中最大的一个的危险程度不超过 n / 2 n/2 n/2。为达到最好的效果,他们将按顺序打击掉编号 1 1 1到 k k k的犯罪团伙,请编程求出 k k k的最小值。
第一行一个正整数 n n n。接下来的 n n n行每行有若干个正整数,第一个整数表示该行除第一个外还有多少个整数,若第 i i i行存在正整数 k k k,表示 i i i, k k k两个团伙可以直接联系。
一个正整数,为 k k k的最小值。
7
2 2 5
3 1 3 4
2 2 4
2 2 3
3 1 6 7
2 5 7
2 5 6
1
这道题需要用并查集来维护团伙关系,所以就有了以下这段代码:
for(int i = 1; i <= n; i ++){
int k;
scanf("%d", &k);
for(int j = 1; j <= k; j ++){
int x;
scanf("%d", &x);
int fx = find(x), fy = find(i);
if(fx != fy){
fa[fx] = fy;
size[fy] += size[fx];
if(size[i] > n / 2){
printf("%d\n", i);
return 0;
}
}
}
}
但很可惜,这段代码是错的,为什么。
假设我们让 i i i从 1 1 1到 n n n枚举,便有了如下过程:
[2]—[1]—[5] − > -> −> [3]—[2]—[1]—[5]
那么我们就应该输出 3 3 3
B u t But But, 非常不幸的是,答案是 1 1 1
所以,我们应该倒着推
[7]—[6]—[5] − > -> −> [7]—[6]—[5]—[1]
那么我们应该输出1吗,这样做对吗,如果对,那为什么对?
对的,让我们换组样例:7 2 2 5 3 1 3 4 2 2 4 2 2 3 3 1 6 7 2 5 7 2 5 6
这里我们按照思路,应输出 4 4 4.
因为从后向前遍历可以保证之前遍历的点不会与其他点连接,所以第一个找到的答案一定是对的.
故可以得出核心代码:
for (int i = n; i; i--)
for (int j = 1; j <= a[i][0]; j++)
if (a[i][j] > i) {
int fx = find(i), fy = find(a[i][j]);
if (fx != fy) {
fa[fy] = fx;
size[fx] += size[fy];
if (size[i] > n / 2) {
printf("%d\n", i);
return 0;
}
}
}
#include
using namespace std;
int fa[1005], a[1005][1005], size[1005];
int find(int x) {
if (fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
fa[i] = i;
size[i] = 1;
}
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i][0]);
for (int j = 1; j <= a[i][0]; j++) scanf("%d", &a[i][j]);
}
for (int i = n; i; i--)
for (int j = 1; j <= a[i][0]; j++)
if (a[i][j] > i) {
int fx = find(i), fy = find(a[i][j]);
if (fx != fy) {
fa[fy] = fx;
size[fx] += size[fy];
if (size[i] > n / 2) {
printf("%d\n", i);
return 0;
}
}
}
return 0;
}
[NOI2002] 银河英雄传说
公元 5801 5801 5801 年,地球居民迁至金牛座 α \alpha α 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。
宇宙历 799 799 799 年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。
杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 30000 30000 30000 列,每列依次编号为 1 , 2 , … , 30000 1, 2,\ldots ,30000 1,2,…,30000。之后,他把自己的战舰也依次编号为 1 , 2 , … , 30000 1, 2, \ldots , 30000 1,2,…,30000,让第 i i i 号战舰处于第 i i i 列,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j
,含义为第 i i i 号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 j j j 号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。
然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。
在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j
。该指令意思是,询问电脑,杨威利的第 i i i 号战舰与第 j j j 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。
作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。
第一行有一个整数 T T T( 1 ≤ T ≤ 5 × 1 0 5 1 \le T \le 5 \times 10^5 1≤T≤5×105),表示总共有 T T T 条指令。
以下有 T T T 行,每行有一条指令。指令有两种格式:
M i j
: i i i 和 j j j 是两个整数( 1 ≤ i , j ≤ 30000 1 \le i,j \le 30000 1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 i i i 号战舰与第 j j j 号战舰不在同一列。
C i j
: i i i 和 j j j 是两个整数( 1 ≤ i , j ≤ 30000 1 \le i,j \le 30000 1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。
依次对输入的每一条指令进行分析和处理:
4
M 2 3
C 1 2
M 2 4
C 4 2
-1
1
战舰位置图:表格中阿拉伯数字表示战舰编号
经典的带权并查集,具体详见代码
#include
using namespace std;
int n, fa[500005], d[500005], size[500005];
int find(int x) {
if(fa[x] == x)return x;
int root = find(fa[x]);
d[x] += d[fa[x]];//加权
return fa[x] = root;//赋值
}
int main(){
for(int i = 1; i <= 30000; i ++){
fa[i] = i;
d[i] = 0;
size[i] = 1;
}//更新
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
char c[3];
int x, y;
scanf("%s",c);
scanf("%d %d", &x, &y);
if(c[0] == 'M'){
int u = find(x), v = find(y);
if(u != v){//合并
fa[u] = v;
d[u] += size[v];
size[v] += size[u];
}
}
else{
if(find(x)!=find(y)) printf("-1\n");
else{
if(d[x] == d[y]) printf("0\n");
else printf("%d\n",abs(d[x]-d[y])-1);
}
}
}
return 0;
}
[NOI2001] 食物链
动物王国中有三类动物 A , B , C A,B,C A,B,C,这三类动物的食物链构成了有趣的环形。 A A A 吃 B B B, B B B 吃 C C C, C C C 吃 A A A。
现有 N N N 个动物,以 1 , 2 , … , n 1,2,\ldots,n 1,2,…,n 编号。每个动物都是 A , B , C A,B,C A,B,C 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 N N N 个动物所构成的食物链关系进行描述:
1 X Y
,表示 X
和 Y
是同类。2 X Y
,表示 X
吃 Y
。此人对 N N N 个动物,用上述两种说法,一句接一句地说出 K K K 句话,这 K K K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
你的任务是根据给定的 N N N 和 K K K 句话,输出假话的总数。
第一行两个整数, N N N, K K K,表示有 N N N 个动物, K K K 句话。
第二行开始每行一句话(按照题目要求,见样例)
一行,一个整数,表示假话的总数。
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
3
1 ≤ N ≤ 5 ∗ 1 0 4 1 ≤ N ≤ 5 ∗ 10^4 1≤N≤5∗104
1 ≤ K ≤ 1 0 5 1 ≤ K ≤ 10^5 1≤K≤105
经典的种类并查集的好题
先让我列个表格(拿2 1 2
举例)
s e l f self self | f o o d food food | e n e m y enemy enemy |
---|---|---|
1 1 1 | 1 + n 1 + n 1+n | 1 + 2 ∗ n 1 + 2 * n 1+2∗n |
2 2 2 | 2 + n 2 + n 2+n | 2 + 2 ∗ n 2 + 2 * n 2+2∗n |
由题 可以知道
2
与1 + n
是同类, 所以将 f a [ 2 ] fa[2] fa[2]置为 1 + n 1 + n 1+n
同理可得,1 + 2 * n
与2 + n
,1
与2 + 2 * n
是同类.
判错只需要判断1
和2 + n
,1
和2
是不是同类就可以了
同理我们可以推广出以下表格:
s e l f self self | f o o d food food | e n e m y enemy enemy |
---|---|---|
x x x | x + n x + n x+n | x + 2 ∗ n x + 2 * n x+2∗n |
y y y | y + n y + n y+n | y + 2 ∗ n y + 2 * n y+2∗n |
可以知道
y
与x + n
,x + 2 * n
与y + n
,x
与y + 2 * n
是同类.
判错只需要判断x
和y + n
,x
和y
是不是同类
当样例为
1 1 3
时,表格如下:
s e l f self self | f o o d food food | e n e m y enemy enemy |
---|---|---|
1 1 1 | 1 + n 1 + n 1+n | 1 + 2 ∗ n 1 + 2 * n 1+2∗n |
2 2 2 | 2 + n 2 + n 2+n | 2 + 2 ∗ n 2 + 2 * n 2+2∗n |
可以知道
1
与2
,1 + n
与2 + n
,1 + 2 * n
与2 + 2 * n
是同类
所以,当要判断这句话是不是错的,只需要判断1
和2 + n
,1
和2 + 2 * n
是不是同类就可以了
同理可推导出
s e l f self self | f o o d food food | e n e m y enemy enemy |
---|---|---|
x x x | x + n x + n x+n | x + 2 ∗ n x + 2 * n x+2∗n |
y y y | y + n y + n y+n | y + 2 ∗ n y + 2 * n y+2∗n |
可以知道
x
与y
,x + n
与y + n
,x + 2 * n
与y + 2 * n
是同类
判错只需要判断x
和y + n
,x
和y + 2 * n
是不是同类就可以了
以上的判错方法其实可以变化 如:
x
与y + 2 * n
可以变成x + n
与y
;
其他的就靠你自己推了.
#include
using namespace std;
int n, k, fa[150005], ans;
int find(int x) {
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d %d", &n, &k);
for(int i = 1; i <= 3 * n; i ++) {
fa[i] = i;
}
for(int i = 1; i <= k; i ++) {
int d, x, y;
scanf("%d %d %d", &d, &x, &y);
if(x > n || y > n || (d == 2 && x == y)){
ans ++;
continue;
}
if(d == 1){
if(find(x) == find(y + n) || find(x) == find(y + 2 * n)){
ans ++;
continue;
}
fa[find(x)] = find(y);
fa[find(x + n)] = find(y + n);
fa[find(x + 2 * n)] = find(y + 2 * n);
}
else {
if(find(x) == find(y + n) || find(x) == find(y)){
ans ++;
continue;
}
fa[find(x)] = find(y + 2 * n);
fa[find(x + n)] = find(y);
fa[find(x + 2 * n)] = find(y + n);
}
}
printf("%d", ans);
return 0;
}
A l i c e Alice Alice和 B o b Bob Bob玩了一个古老的游戏:首先画一个 n × n n × n n×n的点阵(下图 n = 3 n = 3 n=3)
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
直到围成一个封闭的圈(面积不必为 1 1 1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了( n ≤ 200 n ≤ 200 n≤200),他们的游戏实在是太长了!他们甚至在游戏中都不知道谁赢得了游戏。于是请你写一个程序,帮助他们计算他们是否结束了游戏?
输入数据第一行为两个整数 n n n和 m m m。 m m m表示一共画了 m m m条线。以后 m m m行,每行首先有两个数字( x , y x, y x,y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是D
,则是向下连一条边,如果是R
就是向右连一条边。输入数据不会有重复的边且保证正确。
输出一行:在第几步的时候结束。假如 m m m步之后也没有结束,则输出一行 d r a w draw draw
3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D
4
本题考察的是并查集的基本用法,上述问题可以转换为:每个点都是一个单独的集合,如果没有构成一个环,就可以在两个集合之间连一条边,如果在一个集合中,那么就代表这两个集合会成环。
一.将每个坐标看成一个点值,为了方便计算,将所有的坐标横纵坐标都减1,第一个位置即(1,1)
看成是0
,(1,2)
看成是1
,依次类推,将所有的坐标横纵坐标都减1
后,假设当前点是(x,y)
,该点的映射值是a = (x * n + y)
,若向下画,则b = [(x + 1) * n + y]
,若向右画,则b = [x * n + y - 1]
。
二.枚举所有操作,通过并查集操作判断a
和b
是否在同一个集合:
1. 若在同一个集合则标记此操作可以让格子形成环
2.若不在同一个集合,则需要将两个集合进行合并
#include
using namespace std;
int pre[40005], g[205][205], n, m;
int get(int x, int y) {
return g[x][y];
}
int find(int x) {
if (x != pre[x])return pre[x] = find(pre[x]);
return pre[x];
}
int main() {
int tot = 1;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n + 1; i++) {
for (int j = 1; j <= n + 1; j++) {
g[i][j] = tot++;
}
}
int flag = 0;
for (int i = 1; i <= (n + 1) * (n + 1); i++) pre[i] = i;
for (int i = 1; i <= m; i++) {
int a, b, ta, tb;
char c;
scanf("%d %d %c", &a, &b, &c);
if (c == 'D') {
ta = get(a, b);
tb = get(a + 1, b);
}
else {
ta = get(a, b);
tb = get(a, b + 1);
}
int fx = find(ta);
int fy = find(tb);
if (fx == fy) {
printf("%d",i);
flag = 1;
return 0;
}
pre[fx] = fy;
}
printf("draw");
return 0;
}
[BOI2003]团伙
现在有 n n n 个人,他们之间有两种关系:朋友和敌人。我们知道:
现在要对这些人进行组团。两个人在一个团体内当且仅当这两个人是朋友。请求出这些人中最多可能有的团体数。
第一行输入一个整数 n n n 代表人数。
第二行输入一个整数 m m m 表示接下来要列出 m m m 个关系。
接下来 m m m 行,每行一个字符 o p t opt opt 和两个整数 p , q p,q p,q,分别代表关系(朋友或敌人),有关系的两个人之中的第一个人和第二个人。其中 o p t opt opt 有两种可能:
F
,则表明 p p p 和 q q q 是朋友。E
,则表明 p p p 和 q q q 是敌人。一行一个整数代表最多的团体数。
6
4
E 1 4
F 3 5
F 4 6
E 1 2
3
对于 100 % 100\% 100% 的数据, 2 ≤ n ≤ 1000 2 \le n \le 1000 2≤n≤1000, 1 ≤ m ≤ 5000 1 \le m \le 5000 1≤m≤5000, 1 ≤ p , q ≤ n 1 \le p,q \le n 1≤p,q≤n。
类似于
食物链
请自行思考
#include
using namespace std;
int n, m, fa[10005],ans;
bool flag[10005];
int find(int x) {
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 1; i <= 2 * n; i ++){
fa[i] = i;
}
for(int i = 1; i <= m; i ++){
char p;
int x, y;
scanf("%c %d %d\n", &p, &x, &y);
if(p == 'F') {
fa[find(y)] = find(x);
}
else {
fa[find(y)] = find(x + n);
fa[find(x)] = find(y + n);
}
}
for(int i = 1; i <= n; i ++){
if(!flag[find(i)]){
flag[find(i)] = 1;
ans ++;
}
}
printf("%d", ans);
return 0;
}
P1455 搭配购买
明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 n n n 朵云,云朵已经被老板编号为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。
第一行输入三个整数, n , m , w n,m,w n,m,w,表示有 n n n 朵云, m m m 个搭配和你现有的钱的数目。
第二行至 n + 1 n+1 n+1 行,每行有两个整数, c i , d i c_i,d_i ci,di,表示第 i i i 朵云的价钱和价值。
第 n + 2 n+2 n+2 至 n + 1 + m n+1+m n+1+m 行 ,每行有两个整数 u i , v i u_i,v_i ui,vi。表示买第 u i u_i ui 朵云就必须买第 v i v_i vi 朵云,同理,如果买第 v i v_i vi 朵就必须买第 u i u_i 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 n, m, w, c[10005], d[10005], u[10005], v[10005], fa[10005], cnt, dp[100005], flag[10005];
int find(int x) {
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d %d %d", &n, &m, &w);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= n; i ++) scanf("%d %d", &c[i], &d[i]);
for(int i = 1; i <= m; i ++) {
int x, y;
scanf("%d %d", &x, &y);
fa[find(x)] = find(y);
}
for(int i = 1; i <= n; i ++) {
if(!flag[find(i)]){
cnt++;
flag[find(i)] = cnt;
}
u[flag[find(i)]] += c[i];
v[flag[find(i)]] += d[i];
}
for(int i = 1; i <= cnt; i ++)
for(int j = w; j >= u[i]; j --)
dp[j] = max(dp[j], dp[j - u[i]] + v[i]);
printf("%d", dp[w]);
return 0;
}
P2814 家谱
现代的人对于本家族血统越来越感兴趣。
给出充足的父子关系,请你编写程序找到某个人的最早的祖先。
输入由多行组成,首先是一系列有关父子关系的描述,其中每一组父子关系中父亲只有一行,儿子可能有若干行,用 #name
的形式描写一组父子关系中的父亲的名字,用 +name
的形式描写一组父子关系中的儿子的名字;接下来用 ?name
的形式表示要求该人的最早的祖先;最后用单独的一个 $
表示文件结束。
按照输入文件的要求顺序,求出每一个要找祖先的人的祖先,格式为:本人的名字 + + + 一个空格 + + + 祖先的名字 + + + 回车。
#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$
Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur
规定每个人的名字都有且只有 6 6 6 个字符,而且首字母大写,且没有任意两个人的名字相同。最多可能有 1 0 3 10^3 103 组父子关系,总人数最多可能达到 5 × 1 0 4 5 \times 10^4 5×104 人,家谱中的记载不超过 30 30 30 代。
难点在于输入输出,其余就是板题
#include
using namespace std;
int n, m, t;
map<string, string> fa;
string find(string x) {
if (fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
void uset(string x, string y) {
x = find(x), y = find(y);
if (x != y) fa[x] = y;
}
int main() {
char ch;
string s, temp;
while (cin >> ch && ch != '$') {
cin >> s;
if (fa[s] == "") fa[s] = s;
if (ch == '#') temp = s;
else if (ch == '+') uset(s, temp);
else cout << s << " " << find(s) << endl;
}
return 0;
}
警察抓贩毒集团。有不同类型的犯罪集团,人员可能重复,集团内的人会相互接触。现在警察在其中一人(0号)身上搜出毒品,认为与这个人直接接触或通过其他人有间接接触的人都是嫌疑犯。问包括0号犯人共有多少嫌疑犯?
多样例输入。
每个测试用例以两个整数n和m开头,其中n为人数,m为犯罪集团数。你可以假定0 < n <= 30000和0 <= m <= 500。在所有的情况下,每个人都有自己独特的整数编号0到n−1, 且0号是公认的嫌疑犯。
接下来m行输入,每个犯罪集团一行。每一行从一个整数k开始,它本身表示集团内成员的数量。按照成员的数量,在这个组中有k个整数表示人员。一行中的所有整数都被至少一个空格隔开。
n = 0且m = 0时输入结束。
对于每个样例,输出嫌疑犯人数。
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
4
1
1
并查集合并后,在查询即可
#include
using namespace std;
int n, m, k, fa[30005], ans = 1;
int find(int x) {
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
while(scanf("%d %d", &n, &m) && (n || m)){
ans = 1;
for(int i = 0; i < n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) {
scanf("%d", &k);
int x, tmp;
for(int j = 1; j <= k; j ++) {
scanf("%d", &x);
if(j == 1) tmp = x;
fa[find(x)] = find(tmp);
}
}
for(int i = 1; i < n; i ++)
if(find(i) == find(0))
ans ++;
printf("%d\n", ans);
}
return 0;
}
现在有 n n n个岛屿和 m m m座桥。
第i座桥双向连接着第 A i A_{i} Ai和第 B i B_{i} Bi个岛屿。
刚开始的时候,我们可以用其中的一些桥在任何两个岛之间旅行。
然而,经调查显示,这些桥都将会因老旧而倒塌,而且倒塌的顺序为从第 1 1 1座桥到第 M M M座桥。
由于剩下的桥不能够使第 a a a个岛和第 b b b个岛互相到达,这会使岛屿对 ( a , b ) ( a < b ) (a,b)(a(a,b)(a<b)变得很不方便。
对于每一个 i ( 1 ≤ i ≤ m ) i(1 \leq i \leq m) i(1≤i≤m),找出在第i座桥倒塌之后,这样不方便的岛屿对 ( a , b ) ( a < b ) (a,b)(a(a,b)(a<b)有多少?
第一行有两个正整数 N N N和 M M M,分别表示岛屿的数量和桥的数量。
接下来有 M M M行,每一行有两个整数 A i A_{i} Ai和 B i B_{i} Bi,分别表示第 i i i座桥连接的两个岛屿的编号。
输出有 M M M行,每一行输出一个整数,表示第i座桥倒塌之后不方便到达的岛屿对 ( a , b ) ( a < b ) (a,b)(a(a,b)(a<b)的数量,结果可能会超出 32 32 32位 i n t int int的大小。
4 5
1 2
3 4
1 3
2 3
1 4
0
0
4
5
6
6 5
2 3
1 2
5 6
3 4
4 5
8
9
12
14
15
2 1
1 2
1
所有的输入都是整数。
2 ≤ N ≤ 1 0 5 2 \leq N \leq 10^5 2≤N≤105
1 ≤ M ≤ 1 0 5 1 \leq M \leq 10^5 1≤M≤105
1 ≤ A i ≤ B i ≤ N 1 \leq A_{i} \leq B_{i} \leq N 1≤Ai≤Bi≤N
初始不方便到达的岛屿的数量为 0 0 0
此题类似于
打击犯罪
,再运用乘法原理即可
#include
using namespace std;
#define int long long
int n, m, fa[100005], a[100005], b[100005], cnt, tot[100005], sum[100005];
int find(int x) {
if (fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
void uset(int x, int y){
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
signed main(){
scanf("%lld %lld", &n, &m);
for(int i = 1; i <= n; i ++){
sum[i] = 1;
fa[i] = i;
}
tot[m + 1] = n * (n - 1) / 2;
for(int i = 1; i <= m; i ++) scanf("%lld %lld", &a[i], &b[i]);
for(int i = m; i; i --){
int fx = find(a[i]), fy = find(b[i]);
tot[i] = tot[i + 1];
if(fx != fy){
uset(fx, fy);
tot[i] -= sum[fx] * sum[fy];
sum[find(a[i])] = sum[fx] + sum[fy];
}
}
for(int i = 2; i <= m + 1; i ++) printf("%lld\n", tot[i]);
return 0;
}
小Y想要在虚拟世界里造船。最开始 n n n个船的完成度全部都为 0 0 0。小Y第 i i i时刻可以在 a i a_{i} ai和 b i b_{i} bi两艘船中选择一艘让这艘船的完成度 + 1 +1 +1。
由于国家政府是奇数控,所以所有偶数完成度的船只都将被摧毁,小Y想知道 m m m时刻后能剩下来的船只最多有多少艘。
第一行两个整数 n n n和 m m m
接下来 m m m行,每行两个整数 a i , b i a_{i},b_{i} ai,bi
输出仅有一个整数,表示 m m m时刻后能剩下来的船只最多有多少艘。
8 6
1 2
3 4
4 5
6 7
7 8
8 6
6
20%的数据, m ≤ 20 , n ≤ 100 m \leq 20,n \leq 100 m≤20,n≤100
20%的数据, n ≤ 20 , m ≤ 100 n \leq 20,m \leq 100 n≤20,m≤100
20%的数据, n ≤ 100 , m ≤ 500 n \leq 100,m \leq 500 n≤100,m≤500
对于40%的数据, 1 ≤ n , m ≤ 200000 1 \leq n,m \leq 200000 1≤n,m≤200000
这道题我们在每次输入时,构建并查集
(这很普通嘛
在构建并查集时, 我们只需要让子串保留 1 1 1的完成度然后再让根节点变成偶数的完成度或奇数的完成度
对于样例的解释(如下图):
为了方便以下图片的 d i = x d_i = x di=x,皆用一个红色数字 x x x来表示
最后将 d i d_i di加起来就是答案.
这里我们来深思一下四种情况:1.子串不为 1 1 1 且 r o o t root root 不为 1 1 1 – 将子串置为 1 1 1
2.子串不为 1 1 1 且 r o o t root root 为 1 1 1 – 将子串置为 1 1 1
3.子串为 1 1 1 且 r o o t root root 不为 1 1 1 – 将 r o o t root root 置为 1 1 1
4.子串为 1 1 1 且 r o o t root root 为 1 1 1 – 将 r o o t root root 置为 1 1 1
总结起来就是:
1.子串不为 1 1 1 – 子串置为 1 1 1
2.子串为 1 1 1 – r o o t root root 异或等于 1 1 1
注:异或即
xor
与^
作用:将 0 0 0 变成 1 1 1, 将 1 1 1 变成 0 0 0
#include
using namespace std;
int n, m, fa[200005], ans;
bool f[200005];
int find(int x){
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++){
int x, y;
scanf("%d %d", &x, &y);
int fx = find(x), fy = find(y);
if(fx == fy) f[fx] ^= 1;
else {
fa[fx] = fy;
if(f[fx] == 1) f[fy] ^= 1;
else f[fx] = 1;
}
}
for(int i = 1; i <= n; i ++) if(f[i])ans ++;
printf("%d", ans);
return 0;
}
S S S 城现有两座监狱,一共关押着 N N N 名罪犯,编号分别为 1 − N 1-N 1−N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c c c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c c c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S S S 城 Z Z Z 市长那里。公务繁忙的 Z Z Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 N N N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z Z Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
每行中两个数之间用一个空格隔开。第一行为两个正整数 N , M N,M N,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 M M M 行每行为三个正整数 a j , b j , c j a_j,b_j,c_j aj,bj,cj,表示 a j a_j aj 号和 b j b_j bj 号罪犯之间存在仇恨,其怨气值为 c j c_j cj。数据保证 1 < a j ≤ b j ≤ N , 0 < c j ≤ 1 0 9 1
共 1 1 1 行,为 Z Z Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0
。
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
3512
【输入输出样例说明】罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 3512 3512 3512(由 2 2 2 号和 3 3 3 号罪犯引发)。其他任何分法都不会比这个分法更优。
【数据范围】
对于 30 % 30\% 30%的数据有 N ≤ 15 N\leq 15 N≤15。
对于 70 % 70\% 70% 的数据有 N ≤ 2000 , M ≤ 50000 N\leq 2000,M\leq 50000 N≤2000,M≤50000。
对于 100 % 100\% 100% 的数据有 N ≤ 20000 , M ≤ 100000 N\leq 20000,M\leq 100000 N≤20000,M≤100000。
经典的带权并查集,因此排序是必须的,我们要尽可能让危害大的罪犯在两个监狱里。
那么,再结合敌人的敌人和自己在一个监狱的规律合并。
当查找时发现其中两个罪犯不可避免地碰撞到一起时,只能将其输出并结束。
还有一点很重要,就是没有冲突时一定输出 0 0 0 !!!(如果不输出 0 0 0 只会拿 60 60 60 到 90 90 90 分(分数的差异是因为交的题库不一样))
#include
using namespace std;
struct node{
int a, b, c;
bool operator <(const node x)const{
return c > x.c;
}
}a[100005];
int n, m, fa[20005], en[20005];
int find(int x) {
if (fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) scanf("%d %d %d", &a[i].a, &a[i].b, &a[i].c);
sort(a + 1, a + 1 + m);
for(int i = 1; i <= m + 1; i ++){
int fx = find(a[i].a), fy = find(a[i].b);
if(fx == fy){
printf("%d", a[i].c);
return 0;
}
else {
if(!en[a[i].a])en[a[i].a] = a[i].b;
else fa[find(en[a[i].a])] = fy;
if(!en[a[i].b])en[a[i].b] = a[i].a;
else fa[find(en[a[i].b])] = fx;
}
}
return 0;
}
给 n n n组操作,每组操作形式为 x y p
。
当 p p p为 1 1 1时,如果第 x x x个变量和第 y y y个变量可以相等,则输出 YES
,并限制他们相等;否则输出 NO
,并忽略此次操作。
当 p p p为 0 0 0时,如果第 x x x个变量和第 y y y个变量可以不相等,则输出 YES
,并限制他们不相等;否则输出 NO
,并忽略此次操作。
第一行为一个正整数n。 接下来n行每行三个整数 x , y , p x,y,p x,y,p。
对于n行操作,分别输出n行 YES
或者 NO
。
3
1 2 1
1 3 1
2 3 0
YES
YES
NO
对于前 40 % 40\% 40%的数据, n ≤ 1000 n \leq 1000 n≤1000。
对于前 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 , 1 ≤ x , y ≤ 1 0 8 1 \leq n \leq 10^5,1 \leq x,y \leq 10^8 1≤n≤105,1≤x,y≤108 , p ∈ { 0 , 1 } ,p \in \{0,1\} ,p∈{0,1}
看到 x x x 和 y y y 的范围, 我们立刻就想到了离散化
(其实用 m a p map map也不是不行,毕竟我就用的 m a p map map
然后,对于相等的数, 我们用一个并查集来维护,对于每一个不相等的数, 我们都开一个 s e t set set 来维护.
判断方法:
1.当 p p p 为 1 1 1时:
(1)如果 f x fx fx == f y fy fy 输出YES
(2)如果 在 s [ f x ] s[fx] s[fx]中找到了 f y fy fy 输出NO
(3)剩下的情况 输出YES
将 s [ f x ] s[fx] s[fx]与 s [ f y ] s[fy] s[fy]合并, 并合并 f a [ f x ] fa[fx] fa[fx]与 f a [ f y ] fa[fy] fa[fy]
2.当 p p p为 0 0 0时:
(1)如果 f x = = f y fx == fy fx==fy 输出NO
(2)剩下的情况输出YES
,并将 s [ f x ] s[fx] s[fx]与 s [ f y ] s[fy] s[fy]中分别加入 f y , f x fy,fx fy,fx;
#include
using namespace std;
set<int> s[100005];
map<int, int>m;
int n, cnt, fa[100005];
int find(int x) {
if(!fa[x])return x;
else return fa[x] = find(fa[x]);
}
int main(){
set<int>:: iterator it;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
int x, y, p;
scanf("%d %d %d", &x, &y, &p);
if(!m[x]) m[x] = ++ cnt;
if(!m[y]) m[y] = ++ cnt;
int fx = find(m[x]), fy = find(m[y]);
if(!p){
if(fx == fy) printf("NO\n");
else {
printf("YES\n");
s[fx].insert(fy);
s[fy].insert(fx);
}
}
else {
if(s[fx].count(fy)) printf("NO\n");
else if (fx != fy) {
printf("YES\n");
if (s[fx].size() > s[fy].size()) swap(fx, fy);
fa[fx] = fy;
for (set<int>::iterator it = s[fx].begin(); it != s[fx].end(); it++) {
s[fy].insert(*it);
s[*it].erase(fx);
s[*it].insert(fy);
}
}
else printf("YES\n");
}
}
return 0;
}
有 n n n个箱子,初始时每个箱子单独为一列;
接下来有p行输入,M x y
或者 C x
;
对于M x y
:表示将x
箱子所在的一列箱子搬到y
所在的一列箱子上;
对于C x
:表示求箱子x
下面(不包含x
)有多少个箱子;
第一行一个整数 P P P,表示操作次数。
接下来有 p ( 1 ≤ P ≤ 100000 ) p(1 \leq P \leq 100000) p(1≤P≤100000)行输入,M x y
或者 C x
;(x,y<=N)
箱子的个数 N N N,不会出现在输入中。 ( 1 ≤ N ≤ 30000 ) (1 \leq N \leq 30000) (1≤N≤30000),初始时每个箱子单独为一列;你可以认为,操作过程中,不会出现编号大于 30000 30000 30000的箱子
对于每个询问,输出相应的值。
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
1
0
2
跟
银河英雄传说
几乎一样
#include
using namespace std;
int n, fa[500005], d[500005], size[500005];
int find(int x) {
if(fa[x] == x)return x;
int root = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = root;
}
int main(){
for(int i = 1; i <= 30000; i ++){
fa[i] = i;
d[i] = 0;
size[i] = 1;
}
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
char c[3];
int x, y;
scanf("%s",c);
if(c[0] == 'M'){
scanf("%d %d", &x, &y);
int u = find(x), v = find(y);
if(u != v){
fa[u] = v;
d[u] += size[v];
size[v] += size[u];
}
}
else{
scanf("%d", &x);
find(x);
printf("%d\n",d[x]);
}
}
return 0;
}
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 x 1 , x 2 , x 3 , ⋯ x_1,x_2,x_3,\cdots x1,x2,x3,⋯ 代表程序中出现的变量,给定 n n n 个形如 x i = x j x_i=x_j xi=xj 或 x i ≠ x j x_i\neq x_j xi=xj 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为: x 1 = x 2 , x 2 = x 3 , x 3 = x 4 , x 4 ≠ x 1 x_1=x_2,x_2=x_3,x_3=x_4,x_4\neq x_1 x1=x2,x2=x3,x3=x4,x4=x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
输入的第一行包含一个正整数 t t t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第一行包含一个正整数 n n n,表示该问题中需要被满足的约束条件个数。接下来 n n n 行,每行包括三个整数 i , j , e i,j,e i,j,e,描述一个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e = 1 e=1 e=1,则该约束条件为 x i = x j x_i=x_j xi=xj。若 e = 0 e=0 e=0,则该约束条件为 x i ≠ x j x_i\neq x_j xi=xj。
输出包括 t t t 行。
输出文件的第 k k k 行输出一个字符串 YES
或者 NO
(字母全部大写),YES
表示输入中的第 k k k 个问题判定为可以被满足,NO
表示不可被满足。
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
NO
YES
2
3
1 2 1
2 3 1
3 1 1
4
1 2 1
2 3 1
3 4 1
1 4 0
YES
NO
【样例解释1】
在第一个问题中,约束条件为: x 1 = x 2 , x 1 ≠ x 2 x_1=x_2,x_1\neq x_2 x1=x2,x1=x2。这两个约束条件互相矛盾,因此不可被同时满足。
在第二个问题中,约束条件为: x 1 = x 2 , x 1 = x 2 x_1=x_2,x_1 = x_2 x1=x2,x1=x2。这两个约束条件是等价的,可以被同时满足。
【样例说明2】
在第一个问题中,约束条件有三个: x 1 = x 2 , x 2 = x 3 , x 3 = x 1 x_1=x_2,x_2= x_3,x_3=x_1 x1=x2,x2=x3,x3=x1。只需赋值使得 x 1 = x 2 = x 3 x_1=x_2=x_3 x1=x2=x3,即可同时满足所有的约束条件。
在第二个问题中,约束条件有四个: x 1 = x 2 , x 2 = x 3 , x 3 = x 4 , x 4 ≠ x 1 x_1=x_2,x_2= x_3,x_3=x_4,x_4\neq x_1 x1=x2,x2=x3,x3=x4,x4=x1。由前三个约束条件可以推出 x 1 = x 2 = x 3 = x 4 x_1=x_2=x_3=x_4 x1=x2=x3=x4,然而最后一个约束条件却要求 x 1 ≠ x 4 x_1\neq x_4 x1=x4,因此不可被满足。
【数据范围】
注:实际上 n ≤ 1 0 6 n\le 10^6 n≤106 。
并查集的裸题,只需要一个离散化就好了.
#include
using namespace std;
struct node{
int a, b, e;
bool operator <(const node x)const{
return e>x.e;
}
}a[1000005];
int t, n, cnt, fa[3000005], f[3000005];
bool flag = 1;
int find(int x){
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
int main(){
scanf("%d", &t);
while(t--){
cnt = 0;
flag = 1;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
scanf("%d %d %d", &a[i].a, &a[i].b, &a[i].e);
f[++ cnt] = a[i].a;
f[++ cnt] = a[i].b;
}
sort(f + 1, f + 1 + cnt);
int len = unique(f + 1, f + 1 + cnt) - f ;
for(int i = 1; i <= len + 1; i ++)fa[i] = i;
for(int i = 1; i <= n; i ++){
a[i].a = lower_bound(f + 1, f + 1 + len, a[i].a) - f;
a[i].b = lower_bound(f + 1, f + 1 + len, a[i].b) - f;
}
for(int i = 1; i <= n; i ++){
int fx = find(a[i].a), fy = find(a[i].b);
if(a[i].e)fa[fx] = fy;
}
for(int i = 1; i <= n; i ++){
if(!a[i].e && find(a[i].a) == find(a[i].b)){
printf("NO\n");
flag = 0;
break;
}
}
if(flag)printf("YES\n");
}
return 0;
}