奶牛议会 / The Continental Cowngress G
题目链接:ybt金牌导航3-6-3 / luogu P3007
题目大意
有一些人,每个人对众多决案中的两个决案有表示好或不好。
必须要满足每个人两个条件的至少一个。
问你是否有合法解,如果没有判断出来。
有的话你要输出对于每个决案,它是一定要好,还是一定要不要,还是都可以。
思路
首先看着题意都知道它是 2-sat。
建图也很好搞,对于每个人的条件 A,B,那 A 的反向点就连向 B,B 的反向点就连向 A。
但这道题最难的是如何判断每个点的方案是否是一定的。
这里该出两种方法:
每次都 dfs 一次。
什么意思呢,你现在要看
i
i
i 是否一定要或不要,还是都可能。
那你就分别假设,假设
i
i
i 是要的,假设
i
+
n
i+n
i+n(
i
i
i 反向点)是要的(也就是
i
i
i 不要)。
然后你知道如果选了一个它后面遍历到的点都要选,那你就 dfs 遍历一遍,然后你就像判断是否有解一样,你判断一下会不会有一个点和它的反向点都被选了,没有就说明可以,否则就是不可以。
那如果两个点都可以,那就是都可能,否则的就是只能选可以的那个。
只 dfs 一次,我们从入度为
0
0
0 的点出发,然后跑 dfs。
如果你跑的时候你跑的路径上同时出现了一个点和它的反向点,那显然肯定是选后遍历到的(也就是你第二次遍历到的,也就是你发现的位置),那它就必选。
然后你知道搞一个标记,把它可以走向的点都也标记成必选就好了。
那如果两个都不是不选,那就是都可以,否则就是那个必选的。
不过要记得,这两个跑 dfs 都是在缩完点的图上进行的。
然后你会发现第二种方法一个点里面有很多个点,它们每个都有对应的点,可能在缩点后点的不同位置。
而且它们只要有一个在路径中这个点就要选,那你就搞个邻接表,把一个点里面点的对应点所在缩点后的位置都记录下来就可以了。
代码
每次 dfs 判断
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct node {
int to, nxt;
}e[8001], e_[8001];
int n, m, b, c, ba, ca, tmp, tot, le[2001], KK;
int dfn[2001], low[2001], in[2001], sta[2001];
int le_[2001], KK_;
bool inn[2001];
char cc;
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
void tarjan(int now) {
dfn[now] = low[now] = ++tmp;
sta[++sta[0]] = now;
for (int i = le[now]; i; i = e[i].nxt)
if (!dfn[e[i].to]) {
tarjan(e[i].to);
low[now] = min(low[now], low[e[i].to]);
}
else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
if (dfn[now] == low[now]) {
in[now] = ++tot;
while (sta[sta[0]] != now) {
in[sta[sta[0]]] = tot;
sta[0]--;
}
sta[0]--;
}
}
void add_(int x, int y) {
e_[++KK_] = (node){y, le_[x]}; le_[x] = KK_;
}
void dfs(int now) {
inn[now] = 1;
for (int i = le_[now]; i; i = e_[i].nxt)
if (!inn[e_[i].to]) dfs(e_[i].to);
}
int check(int x) {
memset(inn, 0, sizeof(inn));
dfs(in[x]);//直接从这个点开始搜一遍图
for (int i = 1; i <= n; i++)//判断选了它会不会有矛盾产生,不会就说明可以选这个点
if (inn[in[i]] && inn[in[i + n]]) return 0;
return 1;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d", &b);
cc = getchar();
while (cc != 'Y' && cc != 'N') cc = getchar();
if (cc == 'Y') ba = 0;
else ba = 1;
scanf("%d", &c);
cc = getchar();
while (cc != 'Y' && cc != 'N') cc = getchar();
if (cc == 'Y') ca = 0;
else ca = 1;
add((ba ^ 1) * n + b, ca * n + c);
add((ca ^ 1) * n + c, ba * n + b);
}
for (int i = 1; i <= n + n; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++)
if (in[i] == in[n + i]) {
printf("IMPOSSIBLE");
return 0;
}
for (int i = 1; i <= n + n; i++)
for (int j = le[i]; j; j = e[j].nxt)
if (in[i] != in[e[j].to])
add_(in[i], in[e[j].to]);
for (int i = 1; i <= n; i++) {
int ck1 = check(i), ck2 = check(i + n);
if (ck1 && ck2) printf("?");//如果两个都能选就是都行,只有一个就是一定是选这个
else if (ck1) printf("Y");
else printf("N");
}
return 0;
}
一次 dfs 直接全部搜出
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct node {
int to, nxt;
}e[8001], e_[8001], e__[8001];
int n, m, b, c, ba, ca, tmp, tot, le[2001], KK;
int dfn[2001], low[2001], in[2001], sta[2001], ru[2001];
int le_[2001], KK_, le__[2001], KK__, mst[2001];
bool inn[2001], line[2001];
char cc;
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
void tarjan(int now) {
dfn[now] = low[now] = ++tmp;
sta[++sta[0]] = now;
for (int i = le[now]; i; i = e[i].nxt)
if (!dfn[e[i].to]) {
tarjan(e[i].to);
low[now] = min(low[now], low[e[i].to]);
}
else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
if (dfn[now] == low[now]) {
in[now] = ++tot;
while (sta[sta[0]] != now) {
in[sta[sta[0]]] = tot;
sta[0]--;
}
sta[0]--;
}
}
void add_(int x, int y) {
e_[++KK_] = (node){y, le_[x]}; le_[x] = KK_;
ru[y]++;
}
void add__(int x, int y) {//这个邻接表表示这个点包含的点的反点分别在哪些点中
e__[++KK__] = (node){y, le__[x]}; le__[x] = KK__;
}
void dfs(int now, int ans) {
inn[now] = 1;
line[now] = 1;
if (!ans) {
for (int i = le__[now]; i; i = e__[i].nxt)
if (line[e__[i].to]) {//枚举这个点包含的反向点所在的点,如果和它在一条链上你就一定要选(你是后遍历到的)
ans = 1;
break;
}
}
mst[now] = ans;
for (int i = le_[now]; i; i = e_[i].nxt)
if (!mst[e_[i].to]) dfs(e_[i].to, ans);//你选了之后你后面的也要跟着选,所以要把 ans 传递下去
line[now] = 0;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d", &b);
cc = getchar();
while (cc != 'Y' && cc != 'N') cc = getchar();
if (cc == 'Y') ba = 0;
else ba = 1;
scanf("%d", &c);
cc = getchar();
while (cc != 'Y' && cc != 'N') cc = getchar();
if (cc == 'Y') ca = 0;
else ca = 1;
add((ba ^ 1) * n + b, ca * n + c);
add((ca ^ 1) * n + c, ba * n + b);
}
for (int i = 1; i <= n + n; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++)
if (in[i] == in[n + i]) {
printf("IMPOSSIBLE");
return 0;
}
for (int i = 1; i <= n + n; i++)
for (int j = le[i]; j; j = e[j].nxt)
if (in[i] != in[e[j].to])
add_(in[i], in[e[j].to]);
for (int i = 1; i <= n; i++)
add__(in[i], in[i + n]), add__(in[i + n], in[i]);
for (int i = tot; i >= 1; i--)
if (!ru[i]) dfs(i, 0);
for (int i = 1; i <= n; i++) {
if (!mst[in[i]] && !mst[in[i + n]]) printf("?");
else if (!mst[in[i + n]]) printf("Y");
else printf("N");
}
return 0;
}