2-SAT相关

什么是2-SAT?

       其实2-SAT,就是有很多组人,每组有只有两个成员,要你从每组中选一个人。其中,有一些人有矛盾不能同时选。如果抽象的说,就是有n个集合,每个集合有两个元素,从每个集合中选出一个元素,其中有一些元素不能同时选。判定是否有一种方案可以满足上列的条件。

2-SAT的主要问题?

  是否有解,如果有解给出一组可行解,有时有要求字典序最小的解。

主要算法

  对这类问题进行构造建图。下面进行一些约定:我们把假定有i 组人,第i组的两个成员分别为Ai 和 Ai'。如果要选Ai就必须选Aj,那么就把 Ai  指向 Aj。如果Ai和Aj有矛盾,则选Ai 就必须选Aj',选Aj 就必须选Ai'。特别的,如果Ai 和 Ai'中,一定不选Ai,就连一条由Ai指向Ai'的边。2-SAT问题可以转为求有向图的强连通分量和拓扑排序(具体见 由对称性解2-SAT问题)。

下面是算法的步骤:

    1、根据题意建图
    2、求强连通分量( 这有一篇大神写的强连通分量的文章)
    3、拓扑求可行解

算法的模板:

如果有n组人,有m条边那么下面算法的效率为O(m)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//如果不求方案数就要把下面这句删掉
#define REQUE_COLOR 1

//writen by nothi

#ifdef  REQUE_COLOR
const int RED = 1;
const int BULE = 2;
const int WHITE = 0;
#endif

const int maxn = 1100;
//顶点的个数,这里的顶点个数实际上就是组的个数
//组的编号从0到n-1
const int maxn2 = maxn*2;
const int maxm = maxn*maxn;//边的条数

typedef struct Edge{
	int s;
	int e;
	int next;
}Edge;

typedef struct Adj{
	int edge_sum;
	int head[maxn2];
	Edge edge[maxm];

	void initial(){	
		edge_sum = 0;
		memset(head,-1,sizeof(head));
	}

	void add_edge(int a, int b){
		edge[edge_sum].s = a;
		edge[edge_sum].e = b;
		edge[edge_sum].next = head[a];
		head[a] = edge_sum++; 
	} 
}Adj;

#ifdef  REQUE_COLOR
Adj op_adj;
#endif

//顶点序号(组的编号)从0到n-1
//编号为i的组,在实际的算法中组员是2*i 和 2*i+1
//所以对于组员i,和它同组的另一个组员为i^1
//color[belong[u]] == RED 是解
//选调用initial()初始化,再用solve()解决问题
//如果要求一组可行解,调用creat();
typedef struct Two_sat{
	int n;//n是顶点数

	Adj *adj;
	Edge *e;
	int *head;
	int top, cnt, cur;
	int dfn[maxn2];
	int low[maxn2];
	int stack[maxn2];
	int belong[maxn2];	
	int instack[maxn2];

	void initial(Adj* _adj,int _n){
		n = _n;
		adj = _adj;
		e = (*adj).edge;
		head = (*adj).head;
	}

	bool check(){	
		int i, j;
		tarjan();
		for(i = 0; i < 2*n; i += 2)
			if(belong[i] == belong[i^1])
				return false;
		return true;
	}

	int min(int a, int b){
		if(a < b) return a;
		else return b;
	}

	void tarjan(int i){
		int j = head[i];

		dfn[i] = low[i] = ++cur;

		instack[i] = 1;
		stack[top++] = i;

		while(j != -1){
			int u = e[j].e;
			if       (dfn[u] == -1){
				tarjan(u);
				low[i] = min(low[i],low[u]);
			}else if (instack[u])
				low[i] = min(low[i],dfn[u]);
			j = e[j].next; 
		}

		if(dfn[i] == low[i]){
			++cnt;		
			do{
				j = stack[--top];
				instack[j] = 0;
				belong[j] = cnt;
			}while(j != i);
		}
	}

	void tarjan(){
		int i, j;
		cur = cnt = top = 0;
		memset(dfn,-1,sizeof(dfn));
		for(i = 0; i < 2*n; i++)	
			if(dfn[i] == -1)
				tarjan(i);
	}
		
       #ifdef REQUE_COLOR
       //求具体的方案
       int opp[maxn2];
       int color[maxn2];
       int indegree[maxn2];
       
       void creat(){
	       int u, v, i, j;
	       memset(indegree,0,sizeof(indegree));
	       op_adj.initial();

	       for(i = 0; i < 2*n; i += 2){
		       opp[belong[i]] = belong[i^1];
		       opp[belong[i^1]] = belong[i];
	       }

	       for(i = 0; i < (*adj).edge_sum; i++){
			u = belong[e[i].s];
			v = belong[e[i].e];
			if(u == v) continue;
			++indegree[u];
			op_adj.add_edge(v,u);
	       }

	       toposort();
       }

       void toposort(){ 
	       int *head1 = op_adj.head;
	       Edge *edge = op_adj.edge;
	       int i, j, p, now, head, tail,next;

	       head = tail = 0;
	       memset(color,WHITE,sizeof(color));
	       for(i = 1; i <= cnt; i++)
		       if(!indegree[i])
				stack[++head] = i;

	      	while(head != tail){ 
			now = stack[++tail];
			if(color[now] != WHITE)	continue;

		        color[now] = RED;
			color[opp[now]] = BULE;

			p = head1[now];
			while(p != -1){
				next =edge[p].e;	
				--indegree[next];
				if(indegree[next] == 0){
					stack[++head] = next;
				}
				p = edge[p].next;
			}
		}
       }
       #endif
}Two_sat;

Adj adj;
Two_sat ts;




相关问题:

直接的判定问题:
   hdu-1824
         hdu-3602
         hdu-4115
         poj-3678
       
二分答案+2-SAT:
        hdu-1815
        hdu-1816
        hdu-3622
        hdu-3715
        poj-2296
        poj-3207
       
求一组可行解:
        poj-3648
        poj-3683
        poj-3905

求字典序最小的方案:
       hdu-1814
      这题可以看  这里


 转载请注明出处--nothi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值