什么是2-SAT?
SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。
可以证明,当k>2时,k-SAT是NP完全的。因此一般讨论的是k=2的情况,即2-SAT问题。
我们通俗的说,就是给你n个变量ai,每个变量能且只能取0/1的值。同时给出若干条件,形式诸如(not)ai opt (not)aj=0/1
其中opt表示and,or,xor中的一种而求解2-SAT的解就是求出满足所有限制的一组a
通俗点理解:
1、存在n组的元素,每组两个元素。
2、每组元素中,选择了其中一个元素,另外一个元素就不能被选择,两个必须选择一个。这两个元素记为a和!a(两个即为对立面,互补关系)。
3、该模型中的元素之间存在一些关系,且这些关系是对称的。(除非是同一组元素中的关系,这些关系限定了“必须选择”该组中的某一个元素,可能单独出现)
满足上述条件,要求在满足给定关系的情况下在每组元素中选出一个元素,但是选出的元素又是相容的的问题称为2-SAT问题。问是否存在即2-SAT判定问题,当然也可以求出一组可行解。
实现思路:
首先我们考虑将2-SAT问题往图论的方向靠,我们发现每个点要么取0,要么取1。因此对于ai,我们建两个点2i与2i+1分别表示ai取0和1,然后我们考虑建边来表示这些关系,我们令一条有向边的意义:a→b表示如果选择了a就必须选b。
若a和b冲突,即选a时不能选b,那么选a时必须选!b(因为不选b就必须选!b,这是一个2-SAT问题必须满足的条件),那么我们就连边<a,!b>。同样的道理,如果选了b,那么就不能选a,必须选!a,所以连边<b,!a>。这样的连边,显然是对称的。
实现方法:
对原图求一次强连通分量,然后看每组中的两个点是否属于同一个强连通分量,如果存在这种情况,那么无解。
然后对于缩点后的图G',我们将G'中所有边转置。进行拓扑排序。对于缩点后的所有点,我们先预处理求出所有冲突顶点。
例如缩点后Ai所在强连通分支的ID为id[ Ai] ,同理~Ai在 id[ ~Ai ]
所以冲突顶点conflict[ id[Ai] ]=conflict[ id[~Ai] ]同理conflict[ id[~Ai] ]=conflict[ id[Ai] ]
设缩点后有Nscc个点。然后对拓扑序进行染色,初始化所有点color均为未着色,顺序遍历得到的拓扑序列,对于未着色的点x,将x染成红色,同时将所有与x矛盾的点conflic[x]染成蓝色。2-SAT的一组解就等价于所有缩点后点颜色为红色的点,也就是color[ id[i] ]=RED的所有点
资源分享:
网上找的很好的PPT
链接:https://pan.baidu.com/s/1ysYQhtRI2qT4gXYm1tUJ6Q 提取码:da1y参考博客
https://blog.csdn.net/u010126535/article/details/24192565
https://www.cnblogs.com/cjjsb/p/9771868.html
例题POJ3683
题意:
有一个小镇上只有一个牧师。现在有 n 场婚礼需要牧师去主持,每场婚礼的举行时间为 [Si,Ti] ,牧师主持的时间为Di。
但是牧师只能在 [Si,Si+Di] 或者 [Ti-Di,Ti] 这两个区间主持,问牧师是否能够主持所有婚礼,即所有主持时间不冲突,如果可以输出每个婚礼的举行时间。
解题方法:
①先利用上面的方法将所有冲突的关系建立有向边
②利用Tanjar缩点
③记录每个组中两个点的缩点情况
④反向建边和记录入度
⑤拓扑排序染色
⑥输出答案
代码:
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int n;
///时刻区间
struct node
{
int l;
int r;
} time[10005];
int change_time(int h,int m)
{
///时刻换算为分钟
return h*60+m;
}
bool judge(node a,node b)
{
///判断两个区间是否重叠
///如果不重叠返回false
return !(a.r<=b.l||a.l>=b.r) ;
}
///跑Tanjan用的邻接表
struct edge
{
int e;
int p;
} load[4000005];
int head[10005],sign;
void add_edge(int s,int e)
{
load[++sign]=edge{e,head[s]};
head[s]=sign;
}
///Tanjan模板
int dfn[10005],low[10005],t;
int stack_[10005],instack[10005],top;
int belong[10005],cnt;
void tanjan(int s)
{
dfn[s]=low[s]=++t;
stack_[++top]=s;
instack[s]=1;
for(int i=head[s]; i!=-1; i=load[i].p)
{
int e=load[i].e;
if(!dfn[e])
{
tanjan(e);
low[s]=min(low[s],low[e]);
}
else
{
if(instack[e])
low[s]=min(low[s],dfn[e]);
}
}
int now;
if(low[s]==dfn[s])
{
cnt++;
do
{
now=stack_[top--];
instack[now]=0;
belong[now]=cnt;
}
while(now!=s);
}
}
int both[2005];///记录每个点的对立点(比如a和!a)
void solve()
{
printf("YES\n");
int color[10005];///记录拓扑排序的染色情况
int in[10005];///判断入度
memset(in,0,sizeof(in));
memset(color,-1,sizeof(color));
vector<int>q[10005];
///缩点,重新建边
for(int i=2; i<=(n<<1^1); i++)
{
int s=belong[i];
for(int j=head[i]; j!=-1; j=load[j].p)
{
int e=belong[load[j].e];
if(s!=e)
{
q[e].push_back(s);///DAG转置,建立反边
in[s]++;
}
}
}
queue<int>Q;
for(int i=1; i<=cnt; i++)
if(!in[i])
Q.push(i);
while(!Q.empty())
{
int s=Q.front();
Q.pop();
if(color[s]==-1)///如果没有染色
{
color[s]=1; ///当前点染色为1
color[both[s]]=0;///对立点染色为0
}
for(int i=0; i<q[s].size(); i++)
{
int e=q[s][i];
if(--in[e]==0)
Q.push(e);
}
}
for(int i=1; i<=n; i++)
{
int j=i<<1;
if(color[belong[j]]!=1)///如果(i<<1)没有被染色为1,则选择的时他的对立面
j^=1;
printf("%02d:%02d %02d:%02d\n",time[j].l/60,time[j].l%60,time[j].r/60,time[j].r%60);
}
return ;
}
void init()///初始化
{
sign=t=top=cnt=0;
for(int i=1; i<=(n*2+3); i++)
{
head[i]=-1;
dfn[i]=low[i]=0;
instack[i]=0;
}
}
int main()
{
///用i*2和i*2+1表示i的两种情况
while(scanf("%d",&n)!=EOF)
{
init(); ///初始化
int sx,sy,ex,ey,add; ///起始时间和结束时间,牧师举行仪式的时间
int lt,rt;
for(int i=1; i<=n; i++)
{
scanf("%d:%d %d:%d %d",&sx,&sy,&ex,&ey,&add);
///时间换算为分钟
lt=change_time(sx,sy);
rt=change_time(ex,ey);
time[i<<1]=node{lt,lt+add};
time[i<<1^1]=node{rt-add,rt};
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j)
continue;
///建立约束条件
if(judge(time[i<<1],time[j<<1]))
add_edge(i<<1,j<<1^1);
if(judge(time[i<<1],time[j<<1^1]))
add_edge(i<<1,j<<1);
if(judge(time[i<<1^1],time[j<<1]))
add_edge(i<<1^1,j<<1^1);
if(judge(time[i<<1^1],time[j<<1^1]))
add_edge(i<<1^1,j<<1);
}
}
for(int i=2; i<=(n<<1^1); i++)
if(!dfn[i])
tanjan(i);
int flag=1;
for(int i=1; i<=n; i++)
{
///如果两个对立点在同一个强连通分量里,则不存在答案
if(belong[i<<1]==belong[i<<1^1])
{
printf("NO\n");
flag=0;
}
///记录每个点的对立点
both[belong[i<<1]]=belong[i<<1^1];
both[belong[i<<1^1]]=belong[i<<1];
}
if(flag) solve();
}
return 0;
}