2-sat 基本是有三类题型,一种只判定解是否存在,一种判定+二分答案求最佳,一种判定解并输出其中一组解。
Wedding 这题是典型的第三种类型。
ps. 最近太忙了,这题是前天A的,暂时先要把 2-sat 放一放。然后到时候再回来整理下,形成要用到的模板带出去就行了,如果考到就是看临场建图了。
于是这题先记录下来。另外最后输出还是有点疑问的,mark一下,输出的新娘一边,是删除的scc点,还是选择的scc点,即 select[],是一定的吗?跟拓扑的第一个顺序有关?
AC代码:
//POJ-3648 Wedding 2-sat+topsort输出
//n对夫妻。
//i:妻子,i+n:丈夫,
//i+2n:妻子',i+3n:丈夫'。
//原来我一直加错边了!!
//输出的最后一个问题还是不明真相...........
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define MAXN 150 //4倍的点
#define MAXM 20002 //n*n/2
struct node{ int u, v; }a[MAXM];
int first[MAXN], next[MAXM], idx; //idx 边下标
int n; //n对夫妻
int m; //m对奸情
node a2[MAXM];
int f2[MAXN], next2[MAXM], idx2;
void add2(int u, int v) { a2[idx2].u=u, a2[idx2].v=v; next2[idx2]=f2[u]; f2[u]=idx2++; }
void addedge(int u, int v)
{
a[idx].u = u, a[idx].v = v;
next[idx] = first[u];
first[u] = idx++;
}
int dfn[MAXN], low[MAXN];
int stack[MAXN], ins[MAXN];
int belong[MAXN], cnt; //属于什么连通分量,cnt分量总数!
int top, num;
void tarjin(int u)
{
dfn[u] = low[u] = ++num;
ins[u] = 1;
stack[top++] = u;
for(int e=first[u]; e!=-1; e=next[e])
{
int v = a[e].v;
if(!dfn[v])
{
tarjin(v);
low[u] = min(low[u], low[v]);
}
else if(ins[v])
{
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u] == low[u])
{
while(1)
{
int v = stack[--top];
ins[v] = 0;
belong[v] = cnt; //标记分量belong
if(u == v) break;
}
cnt++;
}
}
int pairs[MAXN]; //0/1对。不知道为什么很多模板用 fc[]这个名称呢?
int select[MAXN]; //染色就染色嘛,尼玛就写个col看得我一头雾水...那我select表示选择
int solve()
{
for(int i=0; i<4*n; i++) //4*n 总点数
{
if(!dfn[i])
tarjin(i);
}
for(int i=0; i<2*n; i++) //遍历 女、男 //枚举搭档. 0/1对
{
if(belong[i] == belong[i+2*n]) //若有属于一个分量的。
{
return 0;
} //若不属于,则要记录一下scc的仇恨关系
pairs[belong[i]] = belong[i+2*n];
pairs[belong[i+2*n]] = belong[i];
}
return 1;
}
int read()
{
memset(belong, 0, sizeof(belong));
memset(dfn, 0, sizeof(dfn));
memset(first, -1, sizeof(first));
memset(ins, 0, sizeof(ins));
num = cnt = 0;
top = 0;
idx = 0; //初始化边编号
if(scanf("%d%d", &n, &m)!=EOF && !(!n && !m))
{
for(int i=0; i<n; i++)
{
addedge(i, i+3*n);
addedge(i+3*n, i);
addedge(i+n, i+2*n);
addedge(i+2*n, i+n);
}
addedge(0+2*n, 0); //令新娘在左边。 左为选择。
for(int i=0; i<m; i++)
{
int a1, a2;
char c1, c2;
scanf("%d%c%d%c", &a1, &c1, &a2, &c2);
if(c1=='h') a1+=n; if(c2=='h') a2+=n; //下标转化。女->男
//debug
addedge(a1+2*n, a2); //!!!!!注意这里不要加反??若a1在对面,a2必须在左边(新娘同侧)
addedge(a2+2*n, a1); //
}
return 1;
}
else return 0;
}
int vis[MAXN], topo[MAXN], tot; //tot为拓扑下标
void dfs(int u) //试一下用dfs来topsort, 再改成bfs试下!!
{
vis[u] = 1;
for(int e=f2[u]; e!=-1; e=next2[e])
{
int v = a2[e].v;
if(!vis[v]) dfs(v);
}
if(select[u] == -1) //若u未染色,进行选择删除
{
select[u] = 1; //1为选择
select[pairs[u]] = 0; //0为删除。
}
topo[--tot] = u;
}
void topsort()
{
memset(select, -1, sizeof(select)); //select初始为-1
tot = cnt; //反向缩点图的点数
memset(vis, 0, sizeof(vis));
for(int i=0; i<cnt; i++) //cnt点总数。反向图中,下标为分量下标
if(!vis[i])
dfs(i);
}
void print()
{
idx2 = 0;
memset(f2, -1, sizeof(f2));
for(int i=0; i<idx; i++) //缩点,建图
{
if(belong[a[i].u]!=belong[a[i].v]) //原图的边,若不属于一个分量
{
add2(belong[a[i].v], belong[a[i].u]); //加反向边,belong,分量值当下标。
//printf("insert(%d, %d)\n", belong[a[i].v], belong[a[i].u]);
}
}
topsort();
for(int i=1; i<n; i++) //扫一边。女的
{
if(i!=1) printf(" ");
if(select[belong[i]] == 0) printf("%dw", i); //不是select=1的点,而是为0的??
else printf("%dh", i);
}
printf("\n");
}
int main()
{
while(read())
{
if(solve())
{
// printf("YES\n");
print();
}
else printf("bad luck\n");
}
}