题解 P5782 [POI2001] 和平委员会

题目描述

Link

根据宪法,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;
}
posted @ 2021-05-21 13:00  recollector  阅读(78)  评论(0编辑  收藏  举报