题意:
著名的和平委员会选举问题
委员会必须满足以下条件:
1.每一方在委员会只有一名代表
2.如果两位代表相互讨厌,他们不能同时入选委员会
分析:
经典2 SAT问题
设
(A,A′),(B,B′)
(
A
,
A
′
)
,
(
B
,
B
′
)
如果
A,B
A
,
B
之间存在矛盾:
A−>B′,B−>A′
A
−
>
B
′
,
B
−
>
A
′
其实这道题的重点就在于构造字典序最小解
在蓝书上,lrj表示对于字典序最小解,一般是用暴力构造:
大致过程如下:
- 首先对当前点x进行染色,染为可行,其党派内的对应结点x’则染为不可行
- 访问所有和x相连的结点vi,依次进行搜索
- 如果vi未被染过颜色,同x的染色一样继续进行
如果vi已经被染过色,那么检查它是否可行
如果产生了矛盾,则证明当前染色是错误的,需退回将x’染为可行,x不可行 - 如果重新尝试染Paint(x’)也不行,则意味着无解,退出函数并输出,否则继续依次尝试染色
- 如果所有结点都被染上了颜色,程序结束,访问所有结点,输出所有被染为可行的结点
tip
注意2 SAT中边 (x,y) ( x , y ) 的含义:如果选择了 x x ,我们只能选择
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=20000;
struct node{
int y,nxt;
};
node way[40010];
int n,m,st[N],tot=0,top=0,S[N];
bool col[N];
void add(int u,int w) {
tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}
int fan(int x) {
if (x&1) return x+1;
else return x-1;
}
bool paint(int x) {
if (col[x]) return 1;
if (col[fan(x)]) return 0;
col[x]=1;
S[++top]=x;
for (int i=st[x];i;i=way[i].nxt)
if (!paint(way[i].y)) return 0; //如果染色出现了矛盾,则返回
return 1;
}
bool solve() {
memset(col,0,sizeof(col));
for (int i=1;i<=n*2;i+=2)
if (!col[i]&&!col[i+1]) {
top=0;
if (!paint(i)) {
while (top) col[S[top--]]=0;
if (!paint(i+1)) return 0; //将i的对应点染色
}
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
add(x,fan(y)); add(y,fan(x));
}
if (solve()) {
for (int i=1;i<=n*2;i++)
if (col[i])
printf("%d\n",i);
}
else printf("NIE\n");
return 0;
}