练手用裸题, 用最基本的2-SAT算法即可。 题目要求的是对于任意一组要求至少满足其一, 则不妨设要求的事件分别为xi、xj, 则连一条有向边2i+1->2j, 这里2i+1表示xi为假, 那么如果要满足要求则xj必为真, 同理, 再连一条2j+1->2i, 图的构造完成。
基本思路只要考虑每个没有被赋值的变量就行了, 比如, 点xi未赋值时, 将其赋为真, 并沿从这里发出的边向下搜索同时赋为真, 当出现一个点xj中2j和2j+1均为真时, 说明之前的xi为真不成立, 则重新赋为假。 若当前考虑变量无论为真还是为假都矛盾, 那么该问题无解, 输出"BAD"。
上述方法的具体复杂度我不太清楚, 不过上限应该是O(n²), 用tarjan做的话应该也是可以的, 对于大数据可能更优, 这里不给出tarjan代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000 + 10
#define M 10000 + 10
using namespace std;
struct edge
{
int to, next;
}e[M];
int n, m, num, top, p[N], st[N], flag[N];
int read()
{
char c = getchar();
int x = 0;
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9')
{
(x *= 10) += c - '0';
c = getchar();
}
return x;
}
int exread()
{
char c = getchar();
while(1)
{
if (c == 'm') return 1;
else if (c == 'h') return 0;
c = getchar();
}
}
void add(int x, int y)
{
e[++num].to = y;
e[num].next = p[x];
p[x] = num;
}
void init()
{
int x, y;
top = num = 0;
memset(p, 0, sizeof p);
memset(flag, false, sizeof flag);
n = 2 * read(), m = read();
for (int i = 1; i <= m; ++i)
{
x = exread() + 2 * read();
y = exread() + 2 * read();//瞬间发现了快速读入的神奇用法
add(x ^ 1, y);
add(y ^ 1, x);
}
}
bool dfs(int x)
{
if (flag[x^1]) return false;
if (flag[x]) return true;
flag[x] = true;
st[++top] = x;
for (int i = p[x]; i; i = e[i].next)
if (!dfs(e[i].to)) return false;
return true;
}
void deal()
{
for (int i = 1; i <= n; ++i)
if (!flag[i*2+1] && !flag[i*2])
{
top = 0;
if (!dfs(i*2))
{
while(top > 0) flag[st[top--]] = false;
if (!dfs(i*2+1))
{
printf("BAD\n");
return;
}
}
}
printf("GOOD\n");
}
int main()
{
int t = read();
while(t--)
{
init();
deal();
}
return 0;
}