2-SAT问题简述
前置知识
强连通分量
k-SAT问题
k-SAT问题中的SAT意思就是(stability),也就是适应性问题。本意是给出n个变量,每一个变量有k个状态,并且也给出一些约束条件,要求你求出是否存在每一个变量的取值方案(状态分配方案)。
很可惜,k-SAT(k>2)已经被证明是NP完全的问题了,也就是说我们无法用一些多项式时间复杂度的算法来解决这个问题,但是我们可以发现。当k<=2的时候,我们可以用一些多项式时间复杂度的算法来解决这个问题。
2-SAT问题
先来一道模板题:
下面是对2-sat问题的一个例子
假如你是一个厨师,现在要给两个人做菜,每个人的口味都不同,而你至少要满足每个人的一个口味。这些人会提一些要求,比方说:
A:
- 我不喜欢吃辣椒(-a)
- 我喜欢吃肥肉有(b)
B:
- 我不喜欢吃辣椒(-a)
- 我不喜欢吃肥肉(-b)
那么我们可以用这种形式来简化表示约束条件:
A: -a or B: -a
A: b or B -b
对于每一个\(x_i\) or \(x_j\)的约束条件,我们可以将其变化成:
若\(x_i\)为假则\(x_j\)为真
若\(x_i\)为真则\(x_j\)为假
就比如说 A:-a or B:-b 可以变成
若 A:-a为假,则B: -b为真
或
若 B:-b为假,则A:-a为真
对上面的例子进行缩写,可以得到:
若A为a,则B为-b
和
若B为b,则A为-a
(因为-a为假的话证明肯定就是a了,反之亦然,同时也适用于b的情况)
于是,对于每一种约束条件的格式,我们都可以像上面这样分析,然后发现一些传递的关系:

我们把每一个变量抽象成图上的两个点,两个点分别代表了原来一个变量的两个不同的状态。也就是说变成a 和 -a (就是a真和a假)。
我们可以针对每一种约束条件把相对应的关系传递用有向边连接起来,表示从A的某一个状态可以推测出B的值(或者是b的某一个状态可以推得a的状态),也就是说这会形成一些链,在链上的每一个变量的值都是确定了的。但是根据数据的不同,实际上会出现强连通分量。并且如果一个强连通分量中包括某一个和原来a取值相反的-a的取值,则代表其矛盾,也就是说无解
我们这里使用tarjan求SCC(强连通分量)的算法来检测环的出现与否。
那么当问题有解的时候,我们该如何显示每一个变量的值呢?难道我们还需要对原图进行拓扑+染色吗?实际上我们并不需要这么做。因为我们在tarjan的过程中实际上就已经相当于求了一遍拓扑排序了。可以这么想,因为最后我们赋值scc的时候是从栈内弹出赋值的,也就是说越靠近叶子的节点越先被赋值,总的来看这不就是拓扑序的逆序吗?我们可以直接对比a和-a的拓扑序,哪个小就表明哪个是在越靠经根节点处被选中的,那么我们就优先选择哪个比较小的节点。
当然,因为这里我们存储的是拓扑的逆序,所以我们会优先选择哪个比较大的节点
一些代码处理的细节
虽然我们分析问题的时候经常会用a 或-a来表示某个变量的两种状态,但是数组的下标不能是负数。这里我们可以简单地表示-a为a+n,n就是点数。
如果完全按照每一种xi和xj的取值来写代码的话,就会是这个样子的:
for (int i=1;i<=m;i++) {
int a,va,b,vb;
scanf("%d %d %d %d",&a,&va,&b,&vb);
if (va && vb) { // a, b 都真,-a -> b, -b -> a
gpe[a+n].push_back(b);
gpe[b+n].push_back(a);
} else if (!va && vb) { // a 假 b 真,a -> b, -b -> -a
gpe[a].push_back(b);
gpe[b+n].push_back(a+n);
} else if (va && !vb) { // a 真 b 假,-a -> -b, b -> a
gpe[a+n].push_back(b+n);
gpe[b].push_back(a);
} else if (!va && !vb) { // a, b 都假,a -> -b, b -> -a
gpe[a].push_back(b+n);
gpe[b].push_back(a+n);
}
}
当然这么写是正确的,但是代码不可避免地有点冗长,这里我们可以使用位运算的写法可以缩短代码长度:
for(int i=1;i<=m;i++){
int a,va,b,vb;
scanf("%d %d %d %d",&a,&va,&b,&vb);
gpe[a+n*(va&1)].push_back(b+n*(vb^1));
gpe[b+n*(vb&1)].push_back(a+n*(va^1));
}
模板题AC代码:
//luogu p4782
#include <bits/stdc++.h>
using namespace std;
const int maxn=2000050;
struct edge{
int to;
edge(int to_){
to=to_;
}
};
vector<edge> gpe[maxn];
int dfn[maxn],low[maxn],ins[maxn],scc[maxn],size[maxn],cnt=0,sccn=0;
stack<int> s;
void tarjan(int u){
dfn[u]=low[u]=++cnt;
s.push(u);
ins[u]=1;
for(int i=0;i<gpe[u].size();i++){
int v=gpe[u][i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(ins[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
ins[u]=0;
scc[u]=++sccn;
size[sccn]=1;
while(s.top()!=u){
scc[s.top()]=sccn;
ins[s.top()]=0;
size[sccn]+=1;//这里的size实际上是不需要的
s.pop();
}
s.pop();
}
return;
}
int n,m,oud[maxn];
int main(void){
scanf("%d %d",&n,&m);
memset(low,0x3f,sizeof(low));
memset(ins,0,sizeof(ins));
for(int i=1;i<=m;i++){
int a,va,b,vb;
scanf("%d %d %d %d",&a,&va,&b,&vb);
gpe[a+n*(va&1)].push_back(b+n*(vb^1));
gpe[b+n*(vb&1)].push_back(a+n*(va^1));
}
for(int i=1;i<=n*2;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
if(scc[i]==scc[i+n]){
printf("IMPOSSIBLE");
return 0;
}
}
printf("POSSIBLE\n");
for(int i=1;i<=n;i++){
printf("%d ",scc[i]<scc[i+n]);
}
return 0;
}
个人的xbb
其实这个问题感觉和差分约束有一点神似,因为都是根据关系来转换到图上解决的问题
看到自己以前写的代码感觉还是太蠢了qaq,但是反正也懒得改,就直接贴上去罢了
2-SAT问题简述的更多相关文章
- 简述WebService的使用(二)
上集回顾 上一篇我简单的介绍了一下整个WebService建立和后端访问的过程,如果感兴趣可以看一看:简述WebService的使用(一) //如有不懂请留言,觉得有用请点赞 内容提要 这一篇主要介绍 ...
- 简述 OAuth 2.0 的运作流程
本文将以用户使用 github 登录网站留言为例,简述 OAuth 2.0 的运作流程. 假如我有一个网站,你是我网站上的访客,看了文章想留言表示「朕已阅」,留言时发现有这个网站的帐号才能够留言,此时 ...
- JavaScript单线程和浏览器事件循环简述
JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...
- Design Patterns Simplified - Part 3 (Simple Factory)【设计模式简述--第三部分(简单工厂)】
原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part3-factory/ Design ...
- Android网络定位服务定制简述
Android 添加高德或百度网络定位服务 Android的网络定位服务以第三方的APK方式提供服务,由于在国内Android原生自带的com.google.android.gms服务几乎处于不可用状 ...
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
- 简述ASP.NET MVC原理
1.为什么ASP.NET需要MVC? 因为随着网站的的数量级越来越大,原始的网站方式,这里指的是WebForm,在运行速度和维护性方面,以及代码量上面,越来越难以满足日益庞大的网站维护成本.代码的重构 ...
- Design Patterns Simplified - Part 2 (Singleton)【设计模式简述--第二部分(单例模式)】
原文链接: http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part-2-singleton/ De ...
- 【翻译】设计模式学习系列1---【Design Patterns Simplified: Part 1【设计模式简述:第一部分】】
原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part1/ Design Pattern ...
- Android开发3:Intent、Bundle的使用和ListView的应用 、RelativeLayout(相对布局)简述(简单通讯录的实现)
前言 啦啦啦~博主又来骚扰大家啦~大家是不是感觉上次的Android开发博文有点长呢~主要是因为博主也是小白,在做实验的过程中查询了很多很多概念,努力去理解每一个知识点,才完成了最终的实验.还有就是随 ...
随机推荐
- LeetCode 面试题56 - I. 数组中数字出现的次数 | Python
面试题56 - I. 数组中数字出现的次数 题目 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). ...
- tp5--开发规范
在日常开发的过程中,写代码都要有一定的规范,不然可读取就太差了,所以为了以后的维护.对接,好的代码规定是必须的. 以下是我自己对自己提出的要求: 全部: 1) 每个方法都要写好备注(@retrun作 ...
- Nagios基本搭建
Nagios简述: 1.一款用来监视系统和网络的开源软件 2.利用其从多的插件实现对本机和远端服务的监控 3.当被监控对象异常时,回及时向管理员警告 4.提供一批预设好的监控插件,用户可以直接调用 5 ...
- 利用jsDeliver+github实现免费CDN
title: 利用jsDeliver+github实现免费CDN jsDeliver jsDelivr 是一个免费开源的 CDN 解决方案,用于帮助开发者和站长.包含 JavaScript 库.jQu ...
- QT使用提升自定义组件
QT使用提升自定义组件 QTC++QT自定义 QT 组件提升来实现自定义功能 介绍 我们在使用QT设置界面之后,往往需要自己实现一些方法,如果是单独 的还好,但是如果遇到很多同类型的都有需求, 比如 ...
- 【Linux常见命令】tree命令
tree - list contents of directories in a tree-like format. tree命令用于以树状图列出目录的内容. 执行tree指令,它会列出指定目录下的所 ...
- mac OS 安装 Eclipse
安装Eclipse前先确认你的Mac上是否已安装Java运行环境.进入终端,输入"java -version",如果返回了java版本号则说明已安装 访问Eclipse官方首页ht ...
- 澳大利亚公共服务部门神速完成Win10部署:4个月完成44000台设备升级
不到一年时间,澳大利亚公共服务部门已经完成Win10系统部署升级,涉及到全部的35000名员工.在2015年,澳大利亚公共服务部门IT员工告知微软,需要更创新的方式远程为居民提供服务,并且效率要更快. ...
- 爱创课堂每日一题第五十四天- 列举IE 与其他浏览器不一样的特性?
IE支持currentStyle,FIrefox使用getComputStyle IE 使用innerText,Firefox使用textContent 滤镜方面:IE:filter:alpha(op ...
- #Lab0 Environment Building
清华提供了实验环境的很多选项,具体可以参考README 我选择用虚拟机完成. 一.安装VirtualBox 下载链接 一路next,我的版本是6.0.4. 二.下载虚拟硬盘文件 实验所需的软件都在虚拟 ...