P2024 [NOI2001]食物链
题目链接
题目概述
存在三类动物(A,B,C),他们之间构成(A)吃(B),(B)吃(C),(C)吃(A)的循环食物链关系,现在给出一组输入,总共有(N)中动物,(K)行输入表示这些动物之间的食物关系:
1 X Y
,表示(X)和(Y)是同类动物;2 X Y
,表示(X)吃(Y).
读取这(K)行语句,只要其中一行语句满足下面三个条件之一,这个语句就是假的,否则为真:
- 当前的话与前面的某些真的话冲突,就是假话
- 当前的话中(X)或(Y)比(N)大,就是假话
- 当前的话表示(X)吃(X),就是假话
输出这(K)个语句中假话的个数,输入数据规模:
[1 leq N leq 5cdot 10^4, 1 leq K leq 10^5
]
思路分析
很明显,如果单独处理同一类动物,用一个简单的并查集就可以处理了,但是题目中还限制了一种捕食关系,对于任意一种动物,这个并查集应该能够维护关于结点(X)的下面三种关系:
- X的等价动物集合的代表元素
- X可以捕食的动物的集合的代表元素
- X被捕食的动物的集合的代表元素
我最初是使用了三个并查集,尝试维护这个关系,但是这样的一个关系有一个没法解决的问题,就是这个三个并查集初始时每个位置代表的集合的元素无法完全不同,比如说第一个里面并查集里面(X)表示与动物(X)是同一类动物的代表,但是到了第二个里面又变成了动物(X)可以捕食的代表,这样最终通过查询根节点找到的那个没法有效清晰的表示这个是那种关系的其中一种.一种解决方案以给定动物的种类数量(N)作为偏移量,表示这种动物可以捕食或者被捕食的动物的集合的代表元素.
对于输入的一行约束条件为假,存在以下这样的情况:
- (X > N ,or,Y > N)
- (X)与(Y)是同一类,但是此前得到(X)与(Y)之间存在捕食关系,可能是(X)捕食(Y)也可以是(Y)捕食(X).
- (X)与(Y)存在捕食关系,但是此前的条件可以推出(X)与(Y)是同一类,或者是(Y)可以捕食(X).
如果不满足上面的情况,显然这个约束项应该被加入到里面,根据(X)和(Y)之间的关系不同,更新原来集合关系的操作也不同: - 如果(X)与(Y)是同一类,那么要更新(X)本身和(Y)处于同一个集合,也就是合并两个集合;更新(X)捕食者的集合和(Y)捕食者的集合为同一个集合;更新捕食(X)和捕食(Y)的动物的集合为同一个集合.
- 如果(X)与(Y)之间是捕食关系,那么更新(X)的捕食者为(Y)表示(X)捕食(Y),同时更新捕食(X)的集合为(Y)捕食的动物以及更新集合(X)为捕食(Y)的集合
代码实现
/*
* @Author: Shuo Yang
* @Date: 2020-08-03 19:39:26
* @LastEditors: Shuo Yang
* @LastEditTime: 2020-08-03 19:43:02
* @FilePath: /Code/POJ/1182.cpp
*/
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 1e6+5;
int s[N];
int find_set(int x){
if( s[x] == x)
return x;
int r = x;
while( s[r] != r){
r = s[r];
}
while( x != r){
int t = s[x];
s[x] = r;
x = t;
}
return r;
}
int main(int argc, const char** argv) {
for(int i = 0; i < N; ++i)
s[i] = i;
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,k;
cin>>n>>k;
int ans = 0;
for(int i = 0; i < k; ++i ){
int c,x,y;
cin>>c>>x>>y;
// scanf("%d %d %d", &c,&x,&y);
if(x > n || y > n){
++ans;
continue;
}
if( c == 1){
if(find_set(x + n) == find_set(y) ||find_set( x ) == find_set(y+n)){
++ans;
}else{
s[find_set(x)] = find_set(y);
s[find_set(x+n)] = find_set(y+n);
s[find_set(x+2*n)] = find_set(y+2*n);
}
}else{
if( find_set(x) == find_set(y) || find_set(x+2*n) == find_set(y)){
++ans;
}else{
s[find_set(x+n)] = find_set(y);
s[find_set(x+2*n)] = find_set(y + n);
s[find_set(x)] = find_set(y+2*n);
}
}
}
cout<<ans<<endl;
return 0;
}
在POJ上这道题如果用上面的代码提交会TLE
,但是当把输入换做scanf
时确可以AC
;换到洛谷上速度会比之前的取消同步的cin
快一点.