婚礼 / Wedding
题目链接:ybt金牌导航3-6-2 / luogu UVA11294 / POJ 3648
题目大意
有一堆夫妇,然后有两边,同一对夫妇不能在同一边。
同时再给出一些条件,就是不能让某两个人同时在左边。
问你是否有成立的情况,然后如果有,输出其中一种合法的方案。
思路
首先,你看到题目,自然会想到用 2-set 来做。
然后你考虑建边,对于每一对,有男的在新郎旁边和女的在新郎旁边。
然后如果有冲突,就互相连到互相的另一半。
然后这里有个关键的点就是新娘要连一条边到新郎。
为什么呢?
那你想,只有新郎是有限制的,新娘这边就是随便坐。
那我们 2-set 肯定要让电脑找的是新郎的这边啊。
那怎么弄呢?我们就可以这样连边,这样如果选了新娘就一定要选新郎,就会出现错误。但如果徐娜了新郎就不会有锅。
然后我们来看看怎么找答案 。
2-set 找答案是利用拓扑序,然后还是逆拓扑序,因为按逆的就不会产生冲突,不用逆的有可能会跟前面已经排好的产生冲突。
然后你会发现逆拓扑序其实就是你缩点的顺序,那就不用再跑一遍求了。
然后怎么通过逆拓扑序看放那边呢?
因为你 2-set 选的是新郎的,答案是要新娘的,我们只要到时反过来即可。
然后现在说的是新郎那边的。
那如果对于某一对,那它两个点会各有逆拓扑序。那你肯定是先处理逆拓扑序前的,也就是拓扑序后的,那就选这个点所对应的。
(记得在这道题要反过来!!!)
然后就可以了。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct node {
int to, nxt;
}e[500010];
int n, m, x, y, le[5001], KK;
char cx, cy, answer[5001];
int dfn[5001], low[5001], tmp;
int sta[5001], in[5001], n_n;
bool cant;
void csh() {//初始化
memset(e, 0, sizeof(e));
memset(le, 0, sizeof(le));
KK = 0;
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
tmp = 0;
memset(sta, 0, sizeof(sta));
memset(in, 0, sizeof(in));
n_n = 0;
cant = 0;
}
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
int another(int x) {
if (x > n) return x - n;
return x + n;
}
void tarjan(int now) {//Tarjan缩点
dfn[now] = low[now] = ++tmp;
sta[++sta[0]] = now;
for (int i = le[now]; i; i = e[i].nxt)
if (!dfn[e[i].to]) {
tarjan(e[i].to);
low[now] = min(low[now], low[e[i].to]);
}
else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
if (dfn[now] == low[now]) {
in[now] = ++n_n;
while (sta[sta[0]] != now) {
in[sta[sta[0]]] = n_n;
sta[0]--;
}
sta[0]--;
}
return ;
}
int main() {
scanf("%d %d", &n, &m);
while (n || m) {
csh();
for (int i = 1; i <= m; i++) {
scanf("%d%c %d%c", &x, &cx, &y, &cy);
x++;
y++;
if (cx == 'h') x += n;
if (cy == 'h') y += n;
add(x, another(y));//建图
add(y, another(x));
}
add(1, 1 + n);//让程序选新郎那边的,因为新娘那边没有限制
for (int i = 1; i <= 2 * n; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++)//2-sat 的判断矛盾
if (in[i] == in[n + i]) {
cant = 1;
printf("bad luck\n");
break;
}
if (cant) {
scanf("%d %d", &n, &m);
continue;
}
for (int i = 2; i <= n; i++) {
printf("%d", i - 1);//根据拓扑排序
if (in[i] > in[i + n]) printf("w");
else printf("h");
printf(" ");
}
printf("\n");
scanf("%d %d", &n, &m);
}
return 0;
}