在实际问题中,2-SAT问题在大多数时候表现成以下形式:有N对物品,每对物品中必须选取一个,也只能选取一个,并且它们之间存在某些限制关系(如某两个物品不能都选,某两个物品不能都不选,某两个物品必须且只能选一个,某个物品必选)等,这时,可以将每对物品当成一个布尔值(选取第一个物品相当于0,选取第二个相当于1),如果所有的限制关系最多只对两个物品进行限制,则它们都可以转化成9种基本限制关系,从而转化为2-SAT模型。
在程序实现中,我们把初始的n个物品变成2n个节点,然后从0开始编号到2*n-1号。其中原始第i个物品对应节点i*2和i*2+1。如果我们mark[i*2]节点,那么表示我们i节点设为假,如果我们mark[i*2+1]节点,那么我们i节点设为真。同一个节点只能mark一种结果(即对于原始i来说,我们只能mark[i*2]或mark[i*2+1]其中之一)。
然后加入存在i假或j假的论述,我们就引一条图中从2*i+1到2*j的边,再引一条2*j+1到2*i的边,表示如果i是真的,那么j肯定是假的(否则之前的结论不成立)。且如果j是真的,那么i肯定是假的(否则之前的结论也不成立)。
算法模板:
- #include<cstdio>
- #include<cstring>
- #include<vector>
- using namespace std;
- const int maxn=10000+10;
- struct TwoSAT
- {
- int n;
- vector<int> G[maxn*2];
- bool mark[maxn*2];
- int S[maxn*2],c;
- bool dfs(int x)
- {
- if(mark[x]) return true;
- if(mark[x^1]) return false;
- mark[x]=true;
- S[c++]=x;
- for(int i=0;i<G[x].size();i++)
- if(!dfs(G[x][i])) return false;
- return true;
- }
- void init(int n)
- {
- this->n=n;
- for(int i=0;i<2*n;i++) G[i].clear();
- memset(mark,0,sizeof(mark));
- }
- void add_clause(int x,int xval,int y,int yval)
- {
- x=x*2+xval;
- y=y*2+yval;
- G[x^1].push_back(y);
- G[y^1].push_back(x);
- }
- bool solve()
- {
- for(int i=0;i<2*n;i+=2)
- if(!mark[i] && !mark[i+1])
- {
- c=0;
- if(!dfs(i))
- {
- while(c>0) mark[S[--c]]=false;
- if(!dfs(i+1)) return false;
- }
- }
- return true;
- }
- };