现在的位置: 首页 > 综合 > 正文

2-SAT

2013年01月15日 ⁄ 综合 ⁄ 共 2313字 ⁄ 字号 评论关闭

刷了四天十道2-SAT,基本上知道解决问题的思路了。从这一种题里学会了强连通分量、缩点成树、拓扑排序这么好几个东西,还是有些收获的。
这里留个模板备用,懒得记录解题过程了。。。时间跨度比较大,题目意思都忘了,就那么回事。。。
无非是对于每一对点,看两点之间关系,如果必须同时被选,则加边(a, b), (b, a),不能同时被选(a,~b), (b, ~a);确定某个点必须被选,加(~a, a);某个点必不能选,加(a, ~a),基本上这样就可以将所有的点对以及点自身的关系在有向图上表示出来,用tarjan或者Gabow求强连通分量,如果要输出结果,那么就记录对立点的分量编号,拓排后自底向上(建Dag时反图拓排,按顺序来就行)进行染色,每染一个点将对立分量染成另一种颜色。。。最后按颜色标记答案输出即可。
只做记录用。。。
自己写的模板,各人比较喜欢gabow,在三种强分量算法中码量是最少的,当然也不排除tarjan写的比较好的更短了。。。

class Graph
{
public:
	int nv, ne, to[E], nxt[E], head[V];
	int dage, toDag[E], nxtDag[E], dag[V];
	int idx, scnt, dfn[V], scc[V], oppo[V];
	int in[V], col[V];
	bool vis[V];
	
	int sta[V], top;
	int path[V], pp;
	int q[V], front, rear;

	Graph(const int &v)
		:nv(v)
	{
		init();
	}
	
	void init()
	{
		ne = idx = scnt = 0;
		memset(head, -1, sizeof (head[0]) * nv);
	}
	
	void addEdge(const int &u, const int &v)
	{
		to[ne] = v, nxt[ne] = head[u], head[u] = ne++;
	}
	
	void addDagEdge(const int &u, const int &v)
	{
		toDag[dage] = v, nxtDag[dage] = dag[u], dag[u] = dage++;
		in[v]++;
	}
	
	void initScc()
	{
		top = 0, pp = 0;
		memset(dfn, -1, sizeof (dfn[0]) * nv);
		memset(scc, -1, sizeof (scc[0]) * nv);
		memset(oppo, 0, sizeof (oppo[0]) * nv);
	}
	
	void initDag()
	{
		dage = ne;
		memset(dag, -1, sizeof (dag[0]) * scnt);
		memset(in, 0, sizeof (in[0]) * scnt);
		memset(col, 0, sizeof (col[0])  *scnt);
	}
	
	void gabow(int u)
	{
		int v;
		dfn[u] = idx++;
		sta[top++] = path[pp++] = u;
		for (int i = head[u]; ~i; i = nxt[i])
			if (dfn[v = to[i]] == -1)	gabow(v);
			else if (scc[v] == -1)
				while (dfn[path[pp - 1]] > dfn[v])	pp--;
		if (path[pp - 1] == u)
		{
			pp--;
			do
			{ scc[v = sta[--top]] = scnt; } while (u != v);
			scnt++;
		}
	}
	
	bool checkScc()
	{
		initScc();
		for (int i = 0; i < nv; i++)
			if (dfn[i] == -1)	gabow(i);
		for (int i = 0; i < (nv >> 1); i++)
		{
			if (scc[i << 1] == scc[i << 1 | 1])	return false;
			else
			{
				oppo[scc[i << 1]] = scc[i << 1 | 1];
				oppo[scc[i << 1 | 1]] = scc[i << 1];
			}
		}
		return true;
	}
	
	void buildDag()
	{
		initDag();
		for (int i = 0; i < nv; i++)
			for (int j = head[i]; ~j; j = nxt[j])
				if (scc[i] != scc[to[j]])	addDagEdge(scc[to[j]], scc[i]);
	}
	
	void topoOrder()
	{
		buildDag();
		for (int i = 0; i < scnt; i++)
			if (in[i] == 0)	q[front = (front + 1) % V] = i;
		while (rear != front)
		{
			int t = q[rear = (rear + 1) % V];
			if (!col[t])	col[t] = 1, col[oppo[t]] = -1;
			for (int i = dag[t]; ~i; i = nxtDag[i])
				if (--in[toDag[i]] == 0)	q[front = (front + 1) % V] = toDag[i];
		}
	}
} *gra;

调用时将 gra指针指向的对象new出来,参数是点数,一般是题目给的点数乘二。
init()函数主要为了二分枚举答案(二分啊,两道二分题目全是提交近十次才A啊有木有! o(╯□╰)o果然对边界很不敏感啊,纠结了。)时重新构图方便。
加边用addEdge(u, v),正点用 u << 1,反点用 u << 1 | 1,要是修改加点方法的话checkScc()函数也要跟着改。
判定存不存在用gra->checkScc(),如果只判定不输出答案的话把oppo数组那一部分删掉就可以了,其实前面跟dag有关的,跟color有关的全可以删掉了。
输出答案的话先gra->topoOrder()进行拓排和染色,然后在主叫函数里面判定 gra->col[gra->scc[i << 1]] 的颜色取值就可以输出了。这里的 i 是从 0 循环到 n。
建图很重要,理解题意,理解题意,这个还是慢慢练敏感吧~

抱歉!评论已关闭.