洛谷P1955 [NOI2015]程序自动分析
题目链接
题目概述
存在(n)个变量(x_1,x_2,dots,x_n),给出这(n)个变量之间的(n)个约束条件(x_i = x_j or x_i
eq x_j)判断这些是否可以同时满足,如果可以输出YES
,否则输出NO
.一组测试包含多个测试,输入的开始给出测试的个数,每个测试相互独立.变量之间的约束条件输入的格式为(iquad jquad e),如果(e = 1)说明(x_i = x_j),如果(e=0)说明(x_i
eq x_j.)
题目分析
可以先根据相等的条件把相等的变量划分到一个集合中,然后逐一的判断每个不相等的条件,取出这两个变量(x_i,x_j)如果(x_i,x_j)属于同一个值相等的集合,那么这个约束条件不满足,可以直接输出NO
,当所有的不相等的约束条件都满足时输出YES
.
并查集就是这样一种支持将元素按所属的集合进行分类,可以找到集合中任意一个元素所属的集合,核心操作是union_set(x,y)
和find_set(x)
.以每个集合的树高用于合并时的优化,和查询时采用路径压缩在找到元素所属集合的根节点后,把从根节点到这个元素结点路径上的所有结点的前驱改为根节点,从而在一次(O(log(n)))的时间福再度查询后,下一次可以以(O(1))的时间复杂度获得这个元素的集合的根节点.经过这样的union_set
和find_set
操作,最后得到的并查集的一个类似于这种形式:
注意:但是题目中对于(i,j)范围的限定是(1leq i,j leq 10^9),如果直接开一个(10^{10})的数组,外加一些其它的空间开销,是没法分配这样大的空间的,如果数组开的小,会有一个测试点出现RE
,一个可行的方案是对输入的数据进行离散化处理,输入的数据最多有(2n)个,而(n)的范围是(1leq n leq 10^5),空间开销比较小,假设一组输入的约束条件是:
10000 1999999 1
199999 99999999 0
92713821 23713324 1
10000 23713324 0
很明显这样的数据如果直接用数组下标映射,数组的开销非常大,但实际上输入的数据非常少,把输入的每一个变量存储起来,得到这样的一个序列:
10000 1999999 199999 99999999 92713821 23713324 10000 23713324
然后按升序进行排列去重,得到:
10000 199999 1999999 23713324 92713821 99999999
这样就建立了原来数据和现在数据在序列中下标的映射:
如果需要查询某个数(x)在上面的有序序列中,可以用STL
标准库里面的lower_bound
方法,返回值是区间([first,last))第一个不小于(x)的数在这个区间的位置,如果没有,那么返回(last),因为上面的序列是单调递增的,并且查询的(x)一定位于其中,所以最后得到的一定是数(x)在(buf)中的位置.
经过上面的序列化处理后,利用并查集的模板可以轻松方便的解决这个题目
代码实现
/*
* @Author: Shuo Yang
* @Date: 2020-08-01 08:08:32
* @LastEditors: Shuo Yang
* @LastEditTime: 2020-08-01 10:50:03
* @FilePath: /Code/luogu/P1955.cpp
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+5;
int s[N];
int heights[N];
int buf[N];
//并查集模板
void init_set(){
memset(heights, 0,sizeof(heights));
for(int i = 0;i < N; ++i)
s[i] = i;
}
int find_set(int x){
int r = x;
while( s[r] != r){
r = s[r];
}
int i = x;
while( i != r){
int j = s[i];
s[i] = r;
i = j;
}
return r;
}
void union_set(int x, int y){
x = find_set(x);
y = find_set(y);
if( heights[x] == heights[y]){
heights[x]++;
s[y] = x;
}else{
if( heights[x] < heights[y])
s[x] = y;
else
s[y] = x;
}
}
//这道题实际处理的部分
struct node{
int a,b;
};
node equ[N];
node nequ[N];
int cnt = 0;
int get_index(int x){
return lower_bound(buf, buf+cnt, x) - buf;
}
void solve(int n, int m){
init_set();
for(int i = 0; i < n; ++i){
union_set(get_index(equ[i].a), get_index(equ[i].b));
}
for(int i = 0; i < m; ++i){
if(find_set(get_index(nequ[i].a)) == find_set(get_index(nequ[i].b))){
cout<<"NO"<<endl;
return;
}
}
cout<<"YES"<<endl;
}
int main(int argc, const char** argv) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin>>t;
while(t--){
int k;
cin>>k;
int n = 0;
int m = 0;
cnt = 0;
for(int i = 0; i < k; ++i){
int a,b,f;
cin>>a>>b>>f;
if(f==1){
equ[n++] = {a,b};
}else{
nequ[m++] = {a,b};
}
buf[cnt++] = a;
buf[cnt++] = b;
}
//排序去重
sort(buf, buf+cnt);
cnt = unique(buf, buf+cnt)-buf;
solve(n,m);
}
return 0;
}