[NOI2001] 食物链(扩展域并查集,证明)

[NOI2001] 食物链 - 洛谷

 解题思路

方式和网上很多的都一样,就是建立三个域“A”“B”“C”,对于每个给定的条件,将其中能够提取到的所有条件都在并查集上体现出来(我们可以发现每个结论的得出都是依据这个类型的基本条件“设X为P类可以得出Y为Q类”,而每个条件对应在并查集上连的一条线),每次检测某个情况的时候,比如询问“Z是否吃X”,可以假设Z为A类,看能不能得出X为B类,可以看出假设有可行的推导方式得出这个结论,那么推导过程中所利用到的每一个条件必然在并查集中都有体现(连线),最终能够推出则并查集中从Z_A到X_B之间必然连有一条线。详细见代码中的注释↓

//BE MYSELF.
#include<iostream>
#include<algorithm>
#include<map>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<numeric>
#include<stack>
#include<queue>
#include<set>
#include<string>
#include<deque>
#include<vector>
#include<cstdio>
#include<cctype>
#include<bitset>
#include<cassert>
#define BEMYSELF signed
//#define int long long
#define PQ priority_queue
#define wc(a) cout<<"保持文明*"<<a<<"\n";
#define rep(a, b, c) for(int a = b; a <= (c); a++)
#define drep(a, b, c) for(int a = b; a >= (c); a--)
#define mst(a, b) memset(a, b, sizeof(a));
using namespace std;
typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> pa;
typedef unsigned long long ull;
const double pi=3.14159265358979323846,eps=1e-14;
const int inf=0x3f3f3f3f,int_max=0x7fffffff,maxn=5e4+5;
//这道题实在做过太多遍了。。。开A,B,C三个域,如果X吃Y,则连X_A与Y_B、X_B与Y_C、X_C与Y_A,
//如果X和Y同类则三个域内分别连接。关于这个解法的证明的话,我个人观点如下(由于不是专业
//数学人,所以其实还是没办法100%严谨的):假设某个条件矛盾,比如说“X和Y是同类”,那么
//我们所有能说明它矛盾的方法就有且只有是从前面的条件能够推出“X吃Y”或者“Y吃X”的结论,
//而这些结论也只有是从“某某和某某是同类”,“某某吃某某”的条件推出,那么如果收到一个
//条件之后我就把这个条件能够得到的所有类似上述的条件都给记录下来(如X_A连Y_B表示X是A的
//话Y就是B,Y是B的话X就是A),且这种记录在后面检查的时候只要两者之间可以推出某种关系就
//一定会被显示,这样只要存在矛盾就一定能被及时检测到,就能解决问题了。
//回顾一下这类问题的关键,我觉得就是可以推出矛盾的基本条件都是很少的几条非常类似的条件,
//且是二者绑定的(X_A能推出Y_B的情况下Y_B也能推出X_A,否则单向要2-SAT才能解决),每次收
//到一个条件的时候能从里面分析出几条可以推出某种结论的基本条件都是确定的,而并查集能把
//所有给定的基本条件所能推出的所有信息都检测出来(如果能够通过几条给定条件,从每条里面
//取其中能得出的某条基本条件然后拼凑出一个矛盾结论的话,首先这几条给定条件他们能推出的
//所有基本条件都有在图中进行连线了,而可以看出基本条件的论述其实也是相当于在图上连线(
//肯定是通过比如假设S是某个类型,然后什么什么是什么类型,得出来T是什么类型,从而确定S和
//T的关系。或者说就算是直接描述什么吃什么、什么什么同类我也能够通过这种假设方式来论述,
//并且假设成哪个类型结果都是一样的,至于这个怎么证明我就说不清了,也没必要证明了,要不
//然可能得说到盘古开天辟地了。。。),如果能拼凑得出,就肯定能串成一条,它们肯定在一个
//并查集中,所以能够被检测到)
//自此以后,有关这类问题就做得随心一点了,感觉一直追究下去一直都是说不清的,毕竟没有学
//一些经典的证明方法什么的,而网上是完全找不到一个想要证明这些问题的人的,都是得过且过,
//当然肯定有大佬都是会的,可能他们觉得太简单不想说。
//例如有条件X吃Y,Y吃Z,目前我要判断是否Z吃X,那么假设Z为A看能否推出X为B,由于Z为A则可
//以推出Y为C(有连线),Y为C则可以推出X为B(有连线),故可以检测出。就这样了吧。。。。。。
int fa[maxn*3];
inline int find(int a) {
	return a==fa[a]?a:fa[a]=find(fa[a]);
}
inline void unt(int a,int b) {
	a=find(a);
	b=find(b);
	fa[a]=b;
}
inline bool same(int a,int b) {
	return find(a)==find(b);
}
BEMYSELF main() {
	ios_base::sync_with_stdio(0),cin.tie(0);
	iota(fa,fa+maxn*3,0);
	int n,m,cnt=0;
	cin>>n>>m;
	rep(i,1,m) {
		int op,x,y;
		cin>>op>>x>>y;
		if(x>n||y>n) {
			cnt++;
			continue;
		}
		if(op==1) {
			if(same(x,y+n)||same(y,x+n)) { //设X为A推出Y为B,或者设Y为A推出X为B
				cnt++;
				continue;
			}
			unt(x,y);
			unt(x+n,y+n);
			unt(x+2*n,y+2*n);
		} else {
			if(same(x,y)||same(y,x+n)) {
				cnt++;
				continue;
			}
			unt(x,y+n);
			unt(x+n,y+2*n);
			unt(x+2*n,y);
		}
	}
	cout<<cnt;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值