[CF46F]Hercule Poirot Problem

137 篇文章 1 订阅
122 篇文章 0 订阅

题目

传送门 to luogu

思路

感觉非常难做。一边开门、一边关门,让人十分恼火。

但是仔细想想,开门操作只会让人的移动范围变大,所以不影响后面的操作。于是,我们可以认为,一开始只有开门操作,最后才有很多关门操作。

更进一步地,我们如果有门可以打开,我们就一口气打开。反正就算打开多了,我们也可以直接关上。

开门很简单,因为每次用 O ( m ) \mathcal O(m) O(m) 检查所有门,至少会打开一个,所以复杂度 O ( m 2 ) \mathcal O(m^2) O(m2) 。可是怎么把门关上啊?

无非是找一个门作为最先锁上的,两边可以分别满足条件。可如果递归,铁定死啊!

正难则反。处理不了关门,我们就时光倒流,变成开门。假如初状态、末状态可以归为一个局面,那就可行。

如果二者中途有一个状态相同,那么它们分别疯狂开门后的结果肯定仍然相同。于是没有 v i s \tt vis vis 数组,只需要直接比较最终状态。

这里的比较很麻烦。要两张图的连通性完全一样,并且人处于同一个连通块中,钥匙处于同一个连通块中。这里我们就使用了一个笨办法,邻接矩阵暴力比较。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 1005;
class UFS{
	int fa[MaxN];
public:
	void init(int n){
		for(int i=1; i<=n; ++i)
			fa[i] = i;
	}
	int find(int a){
		if(a != fa[a])
			fa[a] = find(fa[a]);
		return fa[a];
	}
	bool combine(int a,int b){
		a = find(a), b = find(b);
		if(a == b) return 0;
		fa[a] = b; return 1;
	}
};

string readName(){
	string s; s.clear();
	char c = getchar();
	for(; c!=' '; c=getchar())
		s.push_back(c);
	return s;
}

int n, m, k;
int u[MaxN], v[MaxN]; // 边的端点
int key[2][MaxN]; // 钥匙所在的位置
int ren[2][MaxN]; // 人所在的位置

bool linked[2][MaxN][MaxN];
/** @param id 0 or 1 ,表示 初状态 or 末状态 */
void openDoor(int id){
	UFS jury; jury.init(n);
	for(int i=0; i<m; ++i){
		bool ok = true;
		for(int j=1; j<=m; ++j){
			int p = jury.find(key[id][j]);
			if(p == jury.find(u[j]) || p == jury.find(v[j]))
				ok = false, jury.combine(u[j],v[j]);
		}
		if(ok) break; // 无门可以开
	}
	for(int i=1; i<=n; ++i)
	for(int j=1; j<=n; ++j)
		linked[id][i][j] = (jury.find(i) == jury.find(j));
}

bool check(){
	/* 连通情况不同则不行 */ ;
	for(int i=1; i<=n; ++i)
	for(int j=1; j<=n; ++j)
	if(linked[0][i][j] != linked[1][i][j])
		return false;
	/* 钥匙不在同一个连通块内则不行 */ ;
	for(int i=1; i<=m; ++i)
	for(int j=1; j<=n; ++j)
	if(linked[0][key[0][i]][j] != linked[1][key[1][i]][j])
		return false;
	/* 人不在同一个连通块中则不行 */ ;
	for(int i=1; i<=k; ++i)
	for(int j=1; j<=n; ++j)
	if(linked[0][ren[0][i]][j] != linked[1][ren[1][i]][j])
		return false;
	/* 否则,可以在内部调整,满足条件 */ ;
	return true;
}

map<string,int> mp; // 人名->编号

int main(){
	n = readint(), m = readint();
	k = readint();
	for(int i=1; i<=m; ++i){
		u[i] = readint();
		v[i] = readint();
	}
	for(int i=1; i<=k; ++i){
		mp[readName()] = i; // 赋予其编号
		int room = readint();
		ren[0][i] = room;
		for(int x=readint(); x; --x)
			key[0][readint()] = room;
	}
	for(int i=1; i<=k; ++i){
		int t = mp[readName()];
		int room = readint();
		ren[1][t] = room;
		for(int x=readint(); x; --x)
			key[1][readint()] = room;
	}
	openDoor(0), openDoor(1);
	puts(check() ? "YES" : "NO");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值