POJ 3648 Wedding(2-sat输出任一组可行解)

原题地址: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;
}

猜你喜欢

转载自blog.csdn.net/yiqzq/article/details/81367224