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");
		}
}