2-SAT 知识小结
2-SAT 问题:
有 n 个变量,每一个变量都是 bool 类型的,除了这 n 个变量以外,我们还有 m 个关系表达式,关系表达式差不多是这样的:
x1 & x2 = false(注意每个表达式只会有两个变量)
问给出 m 个关系表达式后,能否给这 n 个变量找出一个赋值的方法,使得满足所有的表达式;
建边方式:
核心问题只需要考虑有没有解;
对于每个变量都只有两种取值:0 / 1,那么对于每个点,我们把每个变量拆成 true 点和 false 点;
假如我们有一个关系:x1 & x2 = false;
说明 x1 和 x2 中一定有一个为 false;
那么我们可以从 x1 的 true 连向 x2 的 false,从 x2 的 true 连向 x1 的 false;
要注意只有关系明确的时候才能建边;
解释一下为什么这么连边:
如果 x1 的值为 true ,那么如果我们 x2 的值再为 true 的话,就不满足 “ x1 & x2 = false ” 这个式子了,所以如果 x1 为 true 的话是能明确推出 x2 为 false 的;
我们可以从 x2 的 flase 向 x1 的 true 连边吗?这样也能满足关系式;
显然不能,因为如果 x2 为 false 的话,x1 为 true 或 false 都是可以的,这不是明确的关系,我们不能建边;
所以一般的建边方式为:
若 “ xa = p 或 xb = q ” 中至少有一个满足,那么我们建两条有向边:
xa (¬p) -> xb (q)
xb (¬q) -> xa (p)
可以简单总结为:其中一个不成立则另一个一定成立(这是明确的关系);
如果一个变量必须等于 true,那么我们从这个点的 false 连向这个点的 true,表示我们从 false 走也会走到 true,也就是说只能等于 true;
至于建边的时候怎么给点编号嘛,自定义喽,不过我建议大家这样编号(下面的代码都是这么编号的):
这样的话对于一个变量 k,编号为 k 的点代表了这个变量的 false 点,编号为 k+n 的点代表了这个变量的 true 点;
判解方式:
这么判断是否有解?
无解的情况:某一个变量的 false 能走到 true,从 true 也能走到 false,也就是说,某一个变量的两个取值在同一个强连通分量内的话,就说明无解。
求强联通分量的话常用的方法是 Tarjan 。
否则就是有解的情况,然后它一般让你输出一个给所有变量赋值的方法,使其满足所有的关系式;
那么怎么给变量赋值呢?我们来看一个图:
我们发现,从 x1 的 false 出发会走到 x1 的 true ,也就是说 x1 现在只能等于 true 了;同理从 x2 的 true 出发能走到 x2 的 false,说明 x2 只能等于 false;
解释一下吧:
如果我们不将 x1 赋值为 true,而是赋值为 false,那会发生什么呢?
由 x1 的 false 是能明确推出 x2 是为 true 的,但是又有 x2 的 true 能明确推出 x1 为 true,这与刚刚我们将 x1 赋值为 false 是相矛盾的;(你也可以这么理解:假如你在解方程,实在是解不出来了,你就来了个特殊值法,将 x 代入 1 试试看,结果解出来等式不成立,就说明 x ≠ 1);
有了这个性质,就说明在有解的情况下,一个变量的两个取值是有前后推导关系的,也就是一个取值直接或间接的指向了另一个取值;
我们所要选的就是被指向的这个取值,不然会产生像上例那样的矛盾;
在拓扑序上的表现为:我们要在两种取值中选择拓扑序较大的那个值;
所以我们接下来要:缩点 + 拓扑 + 染色
其实我们在 Tarjan 的时候就已经求出了强联通分量的拓扑序了,只不过是反序;
(以我的理解是拓扑序越大的点在一棵树上是越靠近叶节点的,然后越靠近叶节点的那些节点在 Tarjan 的时候是越早被缩点的,所以拓扑序越大的点其所在强联通分量编号越小)
那么我们只要取两个取值中强联通分量编号较小的所对应的值就可以了;(这是保证不会错的,因为有时候两个值取哪个都行)
上代码(P4782 【模板】2-SAT 问题):
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
long long read()
{
char ch=getchar();
long long a=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
ch=getchar();
}
return a*x;
}
const int N=4e6+;
int n,m,a,b,x,y,tim,top,edge_sum,scc_sum;
int dfn[N],low[N],st[N],vis[N],scc[N],head[N];
struct node
{
int to,next;
}A[N];
void add(int from,int to)
{
edge_sum++;
A[edge_sum].next=head[from];
A[edge_sum].to=to;
head[from]=edge_sum;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++tim;
st[++top]=u;
vis[u]=;
for(int i=head[u];i;i=A[i].next)
{
int v=A[i].to;
if(v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
scc_sum++;
while(st[top]!=u)
{
scc[st[top]]=scc_sum;
vis[st[top]]=;
top--;
}
scc[st[top]]=scc_sum;
vis[st[top]]=;
top--;
}
}
int main()
{
n=read();m=read();
for(int i=;i<=m;i++)
{
a=read();x=read(); //第a个数为x或第b个数为y
b=read();y=read();
if(x==&&y==) //"如果第a个数为0或第b个数为0"至少满足其一
{
add(a+n,b); //a=1则b=0
add(b+n,a); //b=1则a=0
}
if(x==&&y==) //"如果第a个数为0或第b个数为1"至少满足其一
{
add(a+n,b+n); //a=1则b=1
add(b,a); //b=0则a=0
}
if(x==&&y==) //"如果第a个数为1或第b个数为0"至少满足其一
{
add(a,b); //a=0则b=0
add(b+n,a+n); //b=1则a=1
}
if(x==&&y==) //"如果第a个数为1或第b个数为1"至少满足其一
{
add(a,b+n); //a=0则b=1
add(b,a+n); //b=0则a=1
}
}
for(int i=;i<=*n;i++) //对每个变量的每种取值进行tarjan
{
if(!dfn[i]) tarjan(i,);
}
for(int i=;i<=n;i++) //判断无解的情况
{
if(scc[i]==scc[i+n]) //同一变量的两种取值在同一强联通分量里,说明无解
{
printf("IMPOSSIBLE\n");
return ;
}
}
printf("POSSIBLE\n"); //否则就是有解
for(int i=;i<=n;i++)
{
if(scc[i]>scc[i+n]) printf("1 "); //强联通分量编号越小 -> 拓扑序越大 -> 越优
else printf("0 ");
}
return ;
}
还有一个几乎和模板题一样的水题,双倍经验,双倍欢乐qwq!
2-SAT 知识小结的更多相关文章
- Android app开发知识小结
Android知识小结 这是一个知识的总结,所以没有详解的讲解. 一.分辨率Android中dp长度.sp字体使用.px像素.in英寸.pt英寸1/72.mm毫米 了解dp首先要知道density,d ...
- C/C++ 位域知识小结
C/C++ 位域知识小结 几篇较全面的位域相关的文章: http://www.uplook.cn/blog/9/93362/ C/C++位域(Bit-fields)之我见 C中的位域与大小端问题 内存 ...
- JAVA 变量 数据类型 运算符 知识小结
---------------------------------------------------> JAVA 变量 数据类型 运算符 知识小结 <------------------ ...
- html5-基本知识小结及补充
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- HTTPS知识小结
HTTPS知识小结 背景1:TCP握手 internet上的两台机器A,B要建立起HTTP连接了,在这之前要先建立TCP连接,情景大概是这样子的: A:你好,我跟你建立一个TCP好吗? B:好啊. A ...
- shell 环境变量的知识小结
环境变量的知识小结:·变量名通常要大写.·变量可以在自身的Shell及子Shell中使用.·常用export来定义环境变量.·执行env默认可以显示所有的环境变量名称及对应的值.·输出时用“$变量名” ...
- 180531-Spring中JavaConfig知识小结
原文链接:Spring中JavaConfig知识小结/ Sring中JavaConfig使用姿势 去掉xml的配置方式,改成用Java来配置,最常见的就是将xml中的 bean定义, scanner包 ...
- javascript之正则表达式基础知识小结
javascript之正则表达式基础知识小结,对于学习正则表达式的朋友是个不错的基础入门资料. 元字符 ^ $ . * + ? = ! : | \ / ( ) [ ] { } 在使用这些符号时需要 ...
- PS修图知识小结
PS修图知识小结 [1]人去除红眼. 1.用红眼工具,框选红眼不分. 2.用椭圆选区工具,选择红眼部分 3..创建调整图层,亮度.对比度.加大对比度. 4.选择红眼部分.创建调整图层,可选颜色,减 ...
- python基础之八:知识小结及补充
一.python2 与python3 的区别 1.在2中print后可带扣号,也可不带,3中必须带,否则报错! #print 'hello python2' print('hello python3' ...
随机推荐
- Computational biological hypothesis generation using "-omics" data
Computational biological hypothesis generation using "-omics" data Forming biological hypo ...
- WebSocket 转
即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳 ...
- Thomas Brinkhoff 基于路网的移动对象生成器的使用[第二版]
Thomas Brinkhoff 基于路网的移动对象生成器的使用 Thomas Brinkhoff 基于路网的移动对象生成器的使用 相关操作的说明 相关文件的说明 运行 导入eclipse后运行时选择 ...
- QSDK与OPENWRT区别
QSDK与OPENWRT区别 来源 https://www.jianshu.com/p/178ae18b2570 QSDK是一种在openwrt的基础上,加入了高通atheros芯片相关资料的一种环境 ...
- js --桥接模式
定义: 将抽象部分与它的实现部分分离,使他们都可以独立的变化. 也就是说,桥接模式里面有两个角色: - 扩充抽象类 - 具体实现类 在写桥接模式之前,想在写一下关于抽象的理解.我觉得抽象这个概念过于抽 ...
- mysql的安装,启动,和基础配置 -----windows版本
下载: 第一步 : 打开网址(进入官网下载) : https://www.mysql.com , 点击downloads之后跳转到https://www.mysql.com/downloads 第二步 ...
- python 版本号比较 重载运算符
# -*- coding: utf-8 -*- class VersionNum(object): """ 版本号比较 默认版本以“.”分割,各位版本位数不超过3 例一: ...
- pom中添加插件打包上传源码
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> ...
- spark-submit python egg 解决三方件依赖问题
假设spark里用到了purl这个三方件,https://github.com/ultrabluewolf/p.url,他还额外依赖futures这个三方件(six的话,anaconda2自带). p ...
- ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制
ClassLoader类加载器 Java 中的类加载器大致可以分成两类: 一类是系统提供的: 引导类加载器(Bootstrap classloader):它用来加载 Java 的核心库(如rt.jar ...