题解 P5782 [POI2001] 和平委员会
题目描述
根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。 此委员会必须满足下列条件:
- 每个党派都在委员会中恰有 \(1\) 个代表。
- 如果 \(2\) 个代表彼此厌恶,则他们不能都属于委员会。
每个党在议会中有 \(2\) 个代表。代表从 \(1\) 编号到 \(2n\) 。 编号为 \(2i-1\) 和 \(2i\) 的代表属于第 \(i\) 个党派。
任务:写一程序读入党派的数量 \(n\) 和 \(m\) 对关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出任意一个合法的委员会的成员表。
\(1 \leq n \leq 8\times 10^3 ,1 \leq m \leq 2\times 10^4\) 。
Solution
把每个党派看成是一个命题( \(2i-1\) 作为假, \(2i\) 作为真)。
若代表 \(a\) 是命题 \(u\) 的取值 \(p\) , \(b\) 是命题 \(v\) 的取值 \(q\) , \(a\) 和 \(b\) 不能同时属于委员会等价于 \(u\) 的取值不是 \(p\) 或者 \(v\) 的取值不是 \(q\) 。
于是就转化成了经典的 2-SAT 问题,直接求解即可。
时间复杂度 \(\mathcal{O}(n+m)\) ,代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 16005 ,M = 40005;
struct Edge {
int to ,next;
Edge (int to = 0 ,int next = 0) : to(to) ,next(next) {}
}G[M]; int head[N] ,idx;
inline void add(int u ,int v) {
G[++idx] = Edge(v ,head[u]); head[u] = idx;
}
int id[N] ,low[N] ,col[N] ,s[N] ,dfn ,tot ,top;
inline void tarjan(int now) {
id[now] = low[now] = ++dfn;
s[++top] = now;
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (!id[v]) {
tarjan(v);
low[now] = min(low[now] ,low[v]);
}
else if (!col[v]) low[now] = min(low[now] ,id[v]);
}
if (id[now] == low[now]) {
col[now] = ++tot;
while (s[top] != now) col[s[top--]] = tot;
top--;
}
}
int n ,m;
inline int num(int x ,bool k) {
return x + k * n;
}
signed main() {
n = read() ,m = read();
for (int i = 1; i <= m; i++) {
int u = read() ,v = read(); bool a ,b;
if (u & 1) a = false ,u = (u + 1) >> 1;
else a = true ,u = u >> 1;
if (v & 1) b = false ,v = (v + 1) >> 1;
else b = true ,v = v >> 1;
//找出代表所属的命题和取值。
add(num(u ,a) ,num(v ,!b)); // (!a | !b) 等价于 a -> b
add(num(v ,b) ,num(u ,!a)); // 建立反向边
}
for (int i = 1; i <= n * 2; i++) if (!id[i]) tarjan(i);
for (int i = 1; i <= n; i++)
if (col[i] == col[i + n]) return puts("NIE") ,0;
for (int i = 1; i <= n; i++)
printf("%d\n" ,col[i] < col[i + n] ? i * 2 - 1 : i * 2);
return 0;
}