JZOJ

题目:


三类动物A、B、C,A吃B,B吃C,C吃A。
给出K句话来描述N个动物(各属于A、B、C三类之一)
之间的关系,格式及意义如下:
1 X Y:表示X与Y是同类; 2 X Y:表示X吃Y。
K句话中有真话有假话,当一句话满足下列三条之一时,
这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话; 2) 当前的话中X或Y比N大,就是假话; 3) 当前的话表示X吃X,就是假话。
求假话的总数。

输入:


第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y

输出:


假话的总数。


一个容易想到的思路:

用二维数组s存放已知关系:
S[X][Y] = -1:表示X与Y关系未知;
S[X][Y] = 0:表示X与Y是同类;
S[X][Y] = 1:表示X吃Y;
S[X][Y] = 2:表示Y吃X。
对每个读入的关系s(x,y),检查S[x][y]:
若S[x][y]=s,则继续处理下一条;
若S[x][y] = -1,则令S[x][y]=s,并更新S[x][i]、
S[i][x]、S[y][i]和S[i][y] (0<i<=n)。
若S[x][y] != s且S[x][y] != -1,计数器加1。


复杂度:


以上算法需要存储一个N×N的数组,空间复杂度为O(N2)。对每一条语句
进行关系判定时间为O(1)加入关系时间为O(N)总的时间复杂度为O(N*K)
0<=N<=50000,0<=K<=100000,复杂度太高。
对于任意a≠b,a、b属于题中N个动物的集合S,
当且仅当S中存在一个有限序列(P1, P2, …, Pm)(m≥0)
使得aP1、P1P2、…、Pm-1Pm、Pmb(或m=0时的ab)之间的相对关系均已确定时,
b对a的相对关系才可以确定。由上面可知,我们不需要保留每对个体之间的
关系,只需要为每对已知关系的个体保留一条
路径aP1P2…Pmb(m≥0)其中aP1、P1P2、…、Pm-1Pm、Pmb之间的关系均为已知。两两关系已知的动物们,构成一个group。

解决方案:

使用并查集:


用结点表示每个动物,边表示动物之间
的关系。采用父结点表示法,在每个结
点中存储该结点与父结点之间的关系。
parent数组: parent[i]表示i的父节点
relation数组:relation[i]表示i和父节点
的关系
初始状态下,每个结点单独构成一棵树。
读入a,b关系描述时的逻辑判断:
分别找到两个结点a、b所在树的根结点ra、
rb,并在此过程中计算a与ra、b与rb之间的
相对关系。
若ra!=rb,此句为真话,将a、b之间的关系加入;
若ra=rb,则可计算出r(a,b)=f( r(a,ra) , r(b,rb) )
若读入的关系与r(a,b)矛盾,则此句为假话,
计数器加1;
若读入的关系与r(a,b)一致,则此句为真话。

code:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 #define N 50010
 7 
 8 struct node {
 9     int pre;
10     int relation;
11 };
12 node p[N];
13 
14 int find(int x) { //查找根结点
15     int temp;
16     if(x == p[x].pre)
17         return x;
18     temp = p[x].pre; //路径压缩
19     p[x].pre = find(temp);
20     p[x].relation = (p[x].relation + p[temp].relation) % 3; //关系域更新
21     return p[x].pre; //根结点
22 }
23 
24 int main() {
25     int n, k;
26     int ope, a, b;
27     int root1, root2;
28     int sum = 0; //假话数量
29     scanf("%d%d", &n, &k);
30     for(int i = 1; i <= n; ++i) { //初始化
31         p[i].pre = i;
32         p[i].relation = 0;
33     }
34     for(int i = 1; i <= k; ++i) {
35         scanf("%d%d%d", &ope, &a, &b);
36         if(a > n || b > n) { //条件2
37             sum++;
38             continue;
39         }
40         if(ope == 2 && a == b) { //条件3
41             sum++;
42             continue;
43         }
44         root1 = find(a);
45         root2 = find(b);
46         if(root1 != root2) { // 合并
47             p[root2].pre = root1;
48             p[root2].relation = (3 + (ope - 1) +p[a].relation - p[b].relation) % 3;
49         } else {
50             if(ope == 1 && p[a].relation != p[b].relation) {
51                 sum++;
52                 continue;
53             }
54             if(ope == 2 && ((3 - p[a].relation + p[b].relation) % 3 != ope - 1)) {
55                 sum++;
56                 continue;
57             }
58         }
59     }
60     printf("%d\n", sum);
61     return 0;
62 }

这是一道并查集的升级运用。

相比并查集的基础运用题目,难度高很多。

posted @ 2019-08-19 16:22  とある科学の超电磁炮  阅读(467)  评论(0编辑  收藏  举报