Wedding —— 2-SAT

UVA11294 Wedding

  • Up to thirty couples will attend a wedding feast, at which they will be seated on either side of a long table. The bride and groom sit at one end, opposite each other, and the bride wears an elaborate headdress that keeps her from seeing people on the same side as her. It is considered bad luck to have a husband and wife seated on the same side of the table. Additionally, there are several pairs of people conducting adulterous relationships (both different-sex and same-sex relationships are possible), and it is bad luck for the bride to see both members of such a pair. Your job is to arrange people at the table so as to avoid any bad luck.

Input

  • The input consists of a number of test cases, followed by a line containing 0 0. Each test case gives n, the number of couples, followed by the number of adulterous pairs, followed by the pairs, in the form "4h 2w" (husband from couple 4, wife from couple 2), or "10w 4w", or "3h 1h". Couples are numbered from 0 to n - 1 with the bride and groom being 0w and 0h.

Output

  • For each case, output a single line containing a list of the people that should be seated on the same side as the bride. If there are several solutions, any one will do. If there is no solution, output a line containing "bad luck".

Sample Input

10 6
3h 7h
5w 3w
7h 6w
8w 3w
7h 3w
2w 5h
0 0

Sample Output

1h 2h 3w 4h 5h 6h 7h 8h 9h

Solve

题目大意

  • (n(nle 30)) 对夫妻分别坐在新娘和新郎两侧,有两个要求:
    1. 夫妻不能坐在同一侧。
    2. 有特殊关系的两个人不能同时坐在新郎那一侧,即可以同时在新娘那一侧,也可以分别在两侧。
  • 给出特殊关系,w表示女方,h表示男方,若可以满足以上条件,则输出一种方案,否则输出 bad luck

解题思路

  • 根据题意的限制方式,易看出这是一道2-SAT问题。
  • 这道题有两种限制,分别来看:
    1. 夫妻不能坐在同一侧 :
      • 具体来讲,限制就是男方在这一侧,那女方必须在另一侧,则需要进行4条限制,详细来说就是(男左-->女右),(男右-->女左),(女左-->男右),(女右-->男左)。
    2. 有特殊关系的两个人不能同时坐在新郎那一侧:
      • 那就是一个人坐在了新郎这边就强制另一个人坐在新娘那边,假设是 x,y 两个人有特殊关系,且新娘在左面,详细就是(x右-->y左),(y右-->x左)。
  • 还有需要注意的是新娘和新郎有区别的,而且题目中其实也提到了新娘、新郎也有可能有特殊关系,所以需要对新娘、新郎强制安排位置。
  • 对于2-SAT问题其实跑完Tarjan后是不需缩点、再建反图、再在反图上跑拓扑的,我们注意到Tarjan的本质其实是Dfs,而一个SCC(强联通分量)的编号大小其实就代表了在反图上的拓扑序,最后判断的时候就选两个状态所在的SCC的编号小的那个,如果在同一个SCC那就是无解了。
  • 详细的看代码里的注释。

Code

//0状态是新娘那边,我也称为左边
//1状态是新郎那边,我也称为右边
//1 ~ n是女方的0状态,n+1 ~ 2n是男方的0状态
//2n+1 ~ n是女方的1状态,3n+1 ~ 4n是男方的1状态
#include <cstdio>
#include <cstring>
#define min(x, y) ((x) < (y) ? (x) : (y))
//宏定义min在需要大量取min的代码中会使时间效率优化许多
using namespace std;
const int N = 400;
struct Edge {
    int next, t;
}e[N];
int head[N], edc;
void Add(int x, int y) {
    e[++edc] = (Edge) {head[x], y};
    head[x] = edc;
}//邻接表存边
int dfn[N], low[N], dfc, stk[N], tp, c[N], cnt;
void Tarjan(int x) {//Tarjan求强联通分量板子
    dfn[x] = low[x] = ++dfc;
    stk[++tp] = x;
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (!dfn[y]) Tarjan(y), low[x] = min(low[x], low[y]);
        else if (!c[y]) low[x] = min(low[x], dfn[y]);
    }
    if (dfn[x] == low[x] && ++cnt)
        while (1) {
            int y = stk[tp--];
            c[y] = cnt;
            if (x == y) break;
        }
}
int n, m, g;
void Init() {
    g = dfc = cnt = tp = edc = 0;
    memset(head, 0, sizeof(head));
    memset(dfn, 0, sizeof(dfn));
    memset(c, 0, sizeof(c));
}
int main() {
    while (1) {
        scanf("%d %d", &n, &m);
        int n2 = n * 2;
        if (!n && !m) break;
        Init();//多组数据请注意初始化
        Add(1 + n2, 1); //强制新娘在左边
        Add(1 + n, 1 + n + n2);//强制新郎在右边
        for (int i = 2; i <= n; ++i) {
            int x = i, y = i + n; 
            Add(x, y + n2); Add(y + n2, x);
            Add(x + n2, y); Add(y, x + n2);
            //对于一对夫妻进行限制
        }
        while (m--) {
            int x, y;
            char a, b;
            scanf("%d%c%d%c", &x, &a, &y, &b);
            x++; y++;
            if (a == 'h') x += n;
            if (b == 'h') y += n;
            Add(x + n2, y); Add(y + n2, x);
            //对有特殊限制的人进行限制
        }
        //n2是n*2
        for (int i = 1; i <= n2 * 2; ++i) 
            if (!dfn[i]) Tarjan(i);
        for (int i = 1; i <= n2 && !g; ++i)
            if (c[i] == c[i+n2]) puts("bad luck"), g = 1;//无解
        if (g) continue;//有多组数据,不能return 0;
        for (int i = 2; i <= n; ++i)
            printf("%d%c ", i - 1, c[i] < c[i+n2] ? 'w' : 'h');
            //如果0状态所在SCC编号小,就在新娘那一侧,否则在新郎那一侧
        puts("");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/shawk/p/13462464.html