【NOI 2015】程序自动分析 并查集与离散化处理
题目描述
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设x1,x2,x3,…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
输入
输入文件的第1行包含1个正整数t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。
接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。
输出
输出文件包括t行。
输出文件的第k行输出一个字符串“YES”或者“NO”(不包含引号,字母全部大写),“YES”表示输入中的第k个问题判定为可以被满足,“NO”表示不可被满足。
测试输入
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
测试输出
NO
YES
提示
在第一个问题中,约束条件为:x1=x2,x1≠x2。这两个约束条件互相矛盾,因此不可被同时满足。
在第二个问题中,约束条件为:x1=x2,x2=x1。这两个约束条件是等价的,可以被同时满足。
1≤n≤1000000
1≤i,j≤1000000000
题目分析
对于本题,目的是判断程序能否满足所有的输入,因为只有相同和不同两种关系,那么我们可以先处理所有的相同的关系,将相同的数对用并查集维护成一个个不相交集合或者相交集合,而后再处理不同的数对,一旦不同的数对在同一个集合中则发生矛盾,输出NO即可,遍历完所有的不同数对发现确实都属于不同的集合则输出YES,本题需要注意的是输入数字较大,需要用离散化进行处理,直接开数组是不行的
注意点
- 关于并查集的建立,本题由于数据量很大,所以需要用到路径压缩,注释的部分是不采用路径压缩时的代码,路径压缩目的能使一个集合在与另一个集合合并前是以一棵深度只有1的树,这样减少了部分查询的消耗(数据量大的时候更为明显)
int find(int x){
if(p[x] == x) return x;
else{
p[x] = find(p[x]);
}
return p[x];
// while(p[x] != x){
// x = p[x];
// }
// return x;
}
- 关于C++ STL中的unique()函数,它的作用是对一个已经排序完成的数组,实现“合并”相邻的相同数字,为什么合并要带引号呢?事实上,合并后的数组长度未发生变化,它合并的过程是不断把后面的元素覆盖前面重复元素(具体就不作讲解了),在对数组执行unique操作时,一般接受两个参数,数组的首地址和尾地址,而它的返回值是完成查重后的数组的有序位最后一个+1的引用,就是说数组1,1,2,2,4,5,5,6通过unique(a, a+8)之后数组就变成了1,2,4,5,6,5,5,6只有前5个是有序的,且int t = unique(a, a+8)存放的是a[5]的引用,所以如果想得到具体是第几个那么我们一般写成int t = unique(a, a + 8) – a;那么t自然而然就是5,下面是代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
int main(){
int a[] = {1, 1, 2, 2, 4, 5, 5, 6};
int t = unique(a, a + 8) - a;
cout<<t<<endl;
cout<<a[t]<<endl;
for(int i = 0; i <= 7; i++){
cout<<a[i]<<endl;
}
return 0;
}
- 关于lower_bound()函数的使用,这是一个高效的方法用二分查找数组中第一次出现某个数的引用,它接收三个参数,数组首地址,数组尾地址,需要查询的数的值,一般我们将查询出的结果减去数组名,则得到一个数组中下标为i的位置是第一次出现查找的number的位置
i = lower_bound(a + 1, a + n + 1, number) - a;
- 离散化:因为数据量i,j很大,而最多有1000000对输入,显然最多有2000000个不同的数字,我们可以构建一个2000000大的数组存放这所有的输入的i,j(尽管它们本身可能大大超出2000000但放在这个数组里却没有任何问题)
本题代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N = 1000005;
int p[N<<1];
int m[N<<1];
int x[N];
int y[N];
int type[N];
int n, cnt, num;
struct Node{ //结构体是用于存放那些不同的数对的i j的第一次在数组中出现的位置
int x, y;
}k[N<<1];
void merge(){ //合并重复元素,实际上是覆盖操作,数组长度未发生变化,temp存放最后一个有序位置的索引(因为-m-1)
sort(m + 1, m + num + 1);
int temp = unique(m + 1, m + num + 1) - m - 1;
for(int i = 1; i <= n; i++){
x[i] = lower_bound(m + 1, m + temp + 1, x[i]) - m; //lowerbound()前两个参数的闭区间和开区间
y[i] = lower_bound(m + 1, m + temp + 1, y[i]) - m; //执行完成后x[i]存放m数组中第一次出现x[i]的位置
}
}
int find(int x){
if(p[x] == x) return x;
else{
p[x] = find(p[x]);
}
return p[x];
// while(p[x] != x){
// x = p[x];
// }
// return x;
}
void Union(int x, int y){
int fx = find(x);
int fy = find(y);
if(fx != fy) p[fy] = fx;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
scanf("%d", &n);
cnt = 0;
num = 0;
for(int i = 1; i <= n; i++){ //对输入的n组输入进行2*n的离散化处理
scanf("%d%d%d", &x[i], &y[i], &type[i]);
m[++num] = x[i];
m[++num] = y[i];
}
for(int i = 1; i <= num; i++) p[i] = i; //p数组存放根
merge();
for(int i = 1; i <= n; i++){
if(type[i] == 1){
Union(x[i], y[i]); //type == 1时 将根合并同时路径压缩,这里要注意,x[i] y[i]
}else{ //存放的是第一次出现位置,只有这样才能在2000000空间中查询1000000000大的数
k[++cnt].x = x[i]; //k结构体数组存放type == 0时数对x[i],y[i]第一次在数组m中出现的位置
k[cnt].y = y[i];
}
}
int flag = 0;
for(int i = 1; i <= cnt; i++){
int fx = find(k[i].x);
int fy = find(k[i].y);
if(fx == fy){ //如果根相同 则说明相等 矛盾
flag = 1;
printf("NO\n");
break;
}
}
if(flag == 0){
printf("YES\n");
}
}
return 0;
}