什么是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;
相关问题:
直接的判定问题:
二分答案+2-SAT:
求一组可行解:
求字典序最小的方案:
这题可以看
这里