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开发博文有点长呢~主要是因为博主也是小白,在做实验的过程中查询了很多很多概念,努力去理解每一个知识点,才完成了最终的实验.还有就是随 ...
随机推荐
- 飞机大战-面向对象-pygame
飞机大战 最近学习了python的面向对象,对面向对象的理解不是很深刻. 面向对象是数据和函数的'打包整理',将相关数据和处理数据的方法集中在一个地方,方便使用和管理. 本着学习的目的,在网上找了这个 ...
- 利用CRM实现电话营销部门的管控 之数据暂缓
每个公司都有相应的电话营销部门,有的公司是使用的集中的Call Center,有的公司则是使用简单的销售软件.不同的公司都有各自运行管理的方法. 此篇文章主要是介绍基于微软Dynamic CRM下的自 ...
- 从零开始创建CocoaPods私有库
为什么要创建CocoaPods私有库? 避免重复的造轮子 节约时间,方便管理自己的代码 精益求精 创建CocoaPods私有库 1.创建私有仓库工程 执行命令pod lib create SmartB ...
- iOS Block 页面传值
为什么80%的码农都做不了架构师?>>> 直接上代码 1.定义block @interface TopTypeCollectionView : UIView @property ...
- values/colors.xml
<color name="abcd">#FFC4C4C4</color> <color name="white">#FFFF ...
- canvas 绘图api的位置问题
很久没碰canvas了,今天因为canvas绘图的为之问题浪费了一些时间. 我们知道canvas的默认宽高是300X150嘛. 实际使用的时候当然是自定义一个高宽啦. 通常我们会习惯性地在js中通过c ...
- resetlogs 打开数据库时新生成日志位置问题
若系统中缺少联机日志,以resetlogs方式重建控制文件,那么当我们以alter database open resetlogs方式打开数据库时,新生成的联机日志会位于何处? 一:下面分别讨论几种情 ...
- 数学--数论--HDU 2802 F(N) 公式推导或矩阵快速幂
Giving the N, can you tell me the answer of F(N)? Input Each test case contains a single integer N(1 ...
- JAVA大数--POJ 1715 大菲波数
Problem Description Fibonacci数列,定义如下: f(1)=f(2)=1 f(n)=f(n-1)+f(n-2) n>=3. 计算第n项Fibonacci数值. Inp ...
- mock 处理接口依赖
1.输出配置文件如下 login.json [{ "request": { "uri": "/login", "method&qu ...