题意:有n间房间,一些房间是上锁的,一些是未上锁的,只有所有门未上锁的时候,所有人才能逃出去,这里有m个开关,每个开关都可以控制多扇门,一旦按下一个开关,控制的门的状态都会发生变化,未上锁的变成上锁,上锁的变成未上锁。求是否存在一种情况使得所有的门都未上锁。
分析:题目中给出了暗示,说每个门只被两个开关控制。
我们考虑门的初始状态,如果是未上锁的,我们要保持它的未上锁状态。
我们假定开关按下为1,开关没按下为2。按两次等价于没按。
如果门未上锁,那么按下了第一个开关,第二个开关也要按下,
它的逆否命题也要添加进去,连一条边,第二个开关没按,那么第一个开关也不能按。
如果第一个开关没按,第二个开关也不能按,
它的逆否命题如果第二个开关按了,第一个开关也要按下。
总共四种情况,连4条边。
上锁的状态我们要变成未上锁,这个同理。
然后用tarjan求有向图的强连通分量算法求出是否存在一个开关既要开,又要关的情况。如果存在,输出NO。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100005;
int r[N];//门的初始状态
vector<int> a[N];
int h[2 * N], e[8 * N], ne[8 * N], idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfn[2 * N], low[2 * N], timestamp;
int stk[2 * N], in_stk[2 * N], top;
int scc_cnt;
int id[2 * N];
int sz[2 * N];
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u, in_stk[u] = true;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j])
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
int y;
++scc_cnt;
do {
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
++sz[scc_cnt];
} while (y != u);
}
}
int main()
{
//rooms switches
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", &r[i]);//门的状态
//m个开关
int d, q;
for (int i = 1; i <= m; ++i)
{
//控制的门数量
scanf("%d", &d);
for (int j = 1; j <= d; ++j)
{
scanf("%d", &q);
a[q].push_back(i);//控制门的开关
}
}
memset(h, -1, sizeof h);
for (int i = 1; i <= n; ++i)
{
if (r[i])//门未锁
{
add(a[i][0] + m, a[i][1] + m);
add(a[i][1], a[i][0]);
add(a[i][0], a[i][1]);
add(a[i][1] + m, a[i][0] + m);
}
else//锁了
{
add(a[i][0] + m, a[i][1]);
add(a[i][1] + m, a[i][0]);
add(a[i][0], a[i][1] + m);
add(a[i][1], a[i][0] + m);
}
}
for (int i = 1; i <= 2 * m; ++i)
{
if (!dfn[i])
{
tarjan(i);
}
}
bool flag = true;
for (int i = 1; i <= m; ++i)
{
if (id[i] == id[i + m])
{
flag = false;
}
}
if (flag)
{
puts("YES");
}
else
{
puts("NO");
}
return 0;
}