题目
思路
感觉非常难做。一边开门、一边关门,让人十分恼火。
但是仔细想想,开门操作只会让人的移动范围变大,所以不影响后面的操作。于是,我们可以认为,一开始只有开门操作,最后才有很多关门操作。
更进一步地,我们如果有门可以打开,我们就一口气打开。反正就算打开多了,我们也可以直接关上。
开门很简单,因为每次用 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;
}