原题地址:http://poj.org/problem?id=3648
例行贴大佬博客
https://blog.sengxian.com/algorithms/2-sat
https://aneureka.github.io/2018/05/23/algorithm-diary-2SAT-problem/
https://blog.csdn.net/jarjingx/article/details/8521690#t6
https://wenku.baidu.com/view/afd6c436a32d7375a41780f2.html
题意:新郎和新娘结婚,来了n-1对夫妻,这些夫妻包括新郎之间有通奸关系(包括男女,男男,女女),我们的目地是为了满足新娘,新娘对面不能坐着一对夫妻,也不能坐着有任何通奸关系的人,另外新郎一定要坐新娘对面。但是输出时输出坐在新娘这一边的人(不需要输出新娘)
思路:难点主要在于如何建图
如果u和v有通奸关系,就连边u->v’,v->u’。有一点需要注意,就是要连一条边0->1
这样如果选了0就必须选1,那么就矛盾了,所以0一定不被选,选出来的就是新郎那一边的.
ac代码如下,打了不少注释
#include <cmath>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <set>
#include <cctype>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define MOD 1e9+7
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define CLR(x,y) memset((x),y,sizeof(x))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
int n, m;
struct edge {
int v, nxt;
} e[maxn];
int head[maxn], tot;
void init() {
CLR(head, -1);
tot = 0;
}
void add_edge(int u, int v) {
e[tot].v = v;
e[tot].nxt = head[u];
head[u] = tot++;
}
int low[maxn], dfn[maxn], belog[maxn], Stack[maxn], instack[maxn];
int index, scc, top;
void tarjan(int u) {//tarjan缩点
low[u] = dfn[u] = ++index;
instack[u] = 1;
Stack[top++] = u;
for(int i = head[u]; ~i; i = e[i].nxt) {
int v = e[i].v;
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(instack[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
int v;
scc++;
do {
v = Stack[--top];
instack[v] = 0;
belog[v] = scc;
} while(u != v);
}
}
bool solver(int n) {//判断是否有可行解
CLR(dfn, 0);
CLR(instack, 0);
index = scc = top = 0;
for(int i = 0; i < n; i++) {
if(!dfn[i]) tarjan(i);
}
for(int i = 0; i < n; i += 2) {
if(belog[i] == belog[i ^ 1]) return 0;//如果对立点在同一个连通分量,那就不符合
}
return 1;
}
int cf[maxn];//用于记录对立关系
vector<int>G[maxn];
int color[maxn];//-1 不选, 1 选
int into[maxn];//记录入度
void solve(int n) {
queue<int>q;
for(int i = 1; i <= scc; i++) G[i].clear();
CLR(color, 0);
CLR(into, 0);
for(int u = 0; u < n; u++) { //反向建边
for(int i = head[u]; ~i; i = e[i].nxt) {
int v = e[i].v;
int t1 = belog[u];
int t2 = belog[v];
if(t1 != t2) {
G[t2].push_back(t1);
into[t1]++;//存储入度,便于后面拓扑排序
}
}
}
for(int i = 0; i < n; i += 2) {//cf记录每个点对立的强连通分量
cf[belog[i]] = belog[i ^ 1];
cf[belog[i ^ 1]] = belog[i];
}
for(int i = 1; i <= scc; i++) {//将入度为为0的入队列
if(into[i] == 0) q.push(i);
}
while(!q.empty()) {
int u = q.front();
q.pop();
if(color[u] == 0) {//染色,1为选择,-1为不选
color[u] = 1;
color[cf[u]] = -1;
}
for(int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
into[v]--;//去掉相连的边
if(into[v] == 0) q.push(v);
}
}
}
//奇数男,偶数女
int main() {
while(~scanf("%d%d", &n, &m), n + m) {
init();
while(m--) {
char a, b; //表示通奸的性别
int x, y; //表示第几对
scanf("%d%c %d%c", &x, &a, &y, &b);
if(a == 'h' && b == 'h') {//男男
add_edge(x << 1 | 1, y << 1);
add_edge(y << 1 | 1, x << 1);
} else if(a == 'h' && b == 'w') {//男女
add_edge(x << 1 | 1, y << 1 | 1);
add_edge(y << 1, x << 1);
} else if(a == 'w' && b == 'h') {//女男
add_edge(x << 1, y << 1);
add_edge(y << 1 | 1, x << 1 | 1);
} else if(a == 'w' && b == 'w') {//女女
add_edge(x << 1, y << 1 | 1);
add_edge(y << 1, x << 1 | 1);
}
}
add_edge(0, 1);//要从新娘连一条边指向新郎,为的是不选新娘,从而使解是新郎这边的
if(!solver(2 * n)) printf("bad luck\n");
else {
solve(2 * n);
for(int i = 1; i < n; i++) {//总共有n对
if(color[belog[i << 1]] == -1) printf("%dw", i);
//因为2-sat求解的是坐在新娘对面的解,而题目要输出的新娘这一边的解,也就是-1才是新娘这一边的解
else printf("%dh", i);
if(i < n - 1) printf(" ");
else printf("\n");
}
}
}
return 0;
}