说白了,这就是一个 $\texttt{2-SAT}$ 的模板题,大致可以分成三个部分。

首先是对于数据的处理。每一个评审员的输入都是两个字符串,而每个字符串都是由字母 $h/m$ 再加上材料编号组成的。因此只需要将其用 %s 读入后分开处理就行了。

然后就是建图的过程。我们可以把汉与满分别看成 $0$ 和 $1$,然后根据两个条件只要满足其一(也就是)的原则进行建图。举个例子,若 $a,b$ 两个数的值均为 $1$,也就是说 $a,b$ 至少有一个数的值为 $1$。所以我们可以确定建出的两条边是 $a = 0$ 推出 $b = 1$ 与 $b = 0$ 推出 $a = 1$。

最后就是使用 $\texttt{tarjan}$ 算法求出强连通分量,然后判断在一个强连通分量中是否存在一种材料的值既有 $0$ 又有 $1$。若出现这种状态,就说明该材料既需要做成汉式,也需要做成满式,因此条件存在矛盾,所以这种情况要输出的是 BAD;否则输出 GOOD

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#define init(x) memset (x,0,sizeof (x))
using namespace std;
const int MAX = 1e3 + 5;
stack <int> s;
bool ok;
int n,m,t,cnt,times,scc_cnt;
int head[MAX << 1],nxt[MAX << 1],to[MAX << 1],dfn[MAX << 1],low[MAX << 1],scc[MAX << 1];
void pre ();
void build (int num1,bool ty1,int num2,bool ty2);
void _add (int u,int v);
void tarjan (int u);
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
scanf ("%d",&t);
while (t--)
{
pre ();//初始化很重要
scanf ("%d%d",&n,&m);
//汉 0 满 1
for (int i = 1;i <= m;++i)
{
char da[1000],db[1000];
int a = 0,b,c = 0,d;
scanf ("%s %s",da,db);
if (da[0] == 'h') b = 0;
else b = 1;
if (db[0] == 'h') d = 0;
else d = 1;
for (int i = 1;i < strlen (da);++i) a = a * 10 + da[i] - '0';
for (int i = 1;i < strlen (db);++i) c = c * 10 + db[i] - '0';
build (a,b,c,d);
}
for (int i = 1;i <= 2 * n;++i)
if (!dfn[i]) tarjan (i);
for (int i = 1;i <= n;++i)//是否存在矛盾
{
if (scc[i] == scc[i + n])
{
ok = 0;
break;
}
}
if (ok) puts ("GOOD");
else puts ("BAD");
}
return 0;
}
void pre ()
{
cnt = times = scc_cnt = 0;
ok = 1;
init (head);init (nxt);init (to);
init (dfn);init (low);init (scc);
}
void build (int num1,bool ty1,int num2,bool ty2)//建图过程
{
if (!ty1 && !ty2)// the first one is false or the second one is false
{
_add (num1,num2 + n);// we know the first one is true so we can infer the second one is false
_add (num2,num1 + n);
}
if (!ty1 && ty2)// the first one is false or the second one is true
{
_add (num1,num2);// we know the first one is true so we can infer the second one is true
_add (num2 + n,num1 + n);
}
if (ty1 && !ty2)// the first one is true or the second one is false
{
_add (num1 + n,num2 + n);// we know the first one is false so we can infer the second one is false
_add (num2,num1);
}
if (ty1 && ty2)// the first one is true or the second one is true
{
_add (num1 + n,num2);// we know the first one is false so we can infer the second one is true
_add (num2 + n,num1);
}
}
void _add (int u,int v)//连边
{
to[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
void tarjan (int u)//强连通分量
{
dfn[u] = low[u] = ++times;
s.push (u);
for (int i = head[u];i;i = nxt[i])
{
int v = to[i];
if (!dfn[v])
{
tarjan (v);
low[u] = min (low[u],low[v]);
}
else if (!scc[v]) low[u] = min (low[u],dfn[v]);
}
if (low[u] == dfn[u])
{
++scc_cnt;
while (1)
{
int x = s.top ();
s.pop ();
scc[x] = scc_cnt;
if (x == u) break;
}
}
}