什么是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)
{///判断两个区间是否重叠///如果不重叠返回falsereturn !(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;     ///当前点染色为1color[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;
}

【2-SAT初学+模板题讲解】POJ3683 Priest John's Busiest Day相关推荐

  1. poj3683 Priest John's Busiest Day

    输出方案的2-sat 直接比较两个点强联通分量的编号,缩完点的图应该是有向无环图,根据原始做法是反图topsort出解,编号小的说明顺序在后,选择这个点符合定义. #include<cstdio ...

  2. POJ - 3683 Priest John's Busiest Day(2-SAT+路径打印)

    题目链接:点击查看 题目大意:现在有n对新人要结婚,每一场婚礼都要请牧师主持一个特殊的仪式,这个仪式必须在婚礼的前d分钟或者最后d分钟进行,现在问能否有一种安排,能让牧师参加到每一场婚礼去主持仪式,输 ...

  3. [题解]POJ 3683 Priest John's Busiest Day

    [Description] John is the only priest in his town. September 1st is the John's busiest day in a year ...

  4. LA 4328 Priest John's Busiest Day (Greedy)

    https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_probl ...

  5. POJ 3683 Priest John's Busiest Day

    2-SAT简单题,判断一下两个开区间是否相交 #include<cstdio> #include<cstring> #include<cmath> #include ...

  6. POJ 3683 Priest John's Busiest Day(2-ST)

    题目链接:http://poj.org/problem?id=3683 题意:有n个婚礼要举行,但是只有一个牧师.第i个婚礼使用牧师的时间长为leni,可以在开始时或结束时使用.问能否使得n个婚礼均举 ...

  7. pku 3683 Priest John's Busiest Day 2-sat判断有误解+输出可行解

    http://poj.org/problem?id=3683 题意: 一个教父,在一天中要给n对新婚夫妇举行婚礼.已知每对夫妇举行婚礼的起始时间Si和终止时间Ti ,教父送祝福的时间要么在Si-> ...

  8. POJ 3683 Priest John's Busiest Day (算竞进阶习题)

    2-SAT 可以把每一次仪式看成变量,0/1的取值分别为开头举行和结尾举行. 转换为2-SAT接受的命题,就是看某一次仪式中有没有重合的时间段,有的话,就按照不冲突的形式连有向边. 然后跑tarjan ...

  9. 2020年信息系统项目管理师真题讲解:基础知识3/3

    2020年信息系统项目管理师真题讲解:基础知识,共75题真题,本实体解析分三篇文章,本篇为第一篇,解析部分有些图片来自<每天一小时,两月拿证>讲义截图,还有解题部分红色字体为本题核心关键词 ...

最新文章

  1. python一行代码的威力
  2. matlab画一个局部放大的图中图
  3. Java8 - 自定义实现体会Future的原理
  4. 据说这份高考卷,只有程序员能得满分!
  5. 3倍根号x加1分之一c语言,用C语言将一个数开根号后再取倒数的方法
  6. python操作excel命令_python操作Excel读写(使用xlrd和xlrt)[转帖]
  7. 全志F1c100s主线linux入坑记录 (4)GT911触摸移植
  8. Print Label with Barcode Using ZPLII
  9. 吉林大学计算机专业扩招吗,我国有所“退步严重”的985大学,为了大盲目扩招,已被考生嫌弃...
  10. Aliyun-OSS海量存储
  11. Lambada表达式的用法
  12. 摩斯电码php源码,利用PHP怎么编写一个摩斯电码生成器
  13. 千里之行,始于驭风——咕咚新款21k驭风跑鞋体验
  14. vs2008 创建对话框MFC应用程序调试c++代码
  15. 3dmax2016软件安装说明
  16. Python获取全部股票数据
  17. 软件测试-如何准备测试数据
  18. VLAN和trunking技术笔记整理
  19. SCAU JAVA期末复习
  20. 维特比算法 python_维特比算法实现分词

热门文章

  1. Unity根据文字内容自动滚动显示最新文字
  2. 仿掘金社区全栈项目开发(二)-前端工程化
  3. .bat文件打开闪退
  4. openLayers6系列理论基础——Layer数据源source篇
  5. Java打印流——PrintStream
  6. 国内外IP黑名单查询网站和邮件相关DNS的查询大全
  7. 深度学习之openvino预训练模型测试(车牌识别)
  8. AIML标签中srai不起作用的原因
  9. DIST@SAU实验室学生博客名单
  10. HTTP代理IP的三种使用方法