2-SAT 学习笔记
2-SAT 用于求解布尔方程组,其中每个方程最多含有两个变量,方程的形式为
建图:
这里以模版题为例:
题意:给定若干个需要满足的条件,其形式为
我们将一个点拆成两个
- 若
为真或 为真:则当 为假时, 一定为真,将 向 连一条有向边;当 为假时, 一定为真,将 向 连一条有向边。 - 若
为真或 为假:则当 为假时, 一定为假,将 向 连一条有向边;当 为真时, 一定为假,将 向 连一条有向边。 - 若
为假或 为真:则当 为真时, 一定为真,将 向 连一条有向边;当 为假时, 一定为假,将 向 连一条有向边。 - 若
为假或 为假:则当 为真时, 一定为假,将 向 连一条有向边;当 为真时, 一定为假,将 向 连一条有向边。
注意连边建图时,一定要将所有的情况全部连起来,并且一定要满足 若
求强联通子图:
这里介绍 Kosaraju 算法。
该算法分为两步:
- 先对原图中任意一个点开始进行 dfs ,直到所有点都被访问过一遍。在 dfs 时,当我们每退出一个点时,就将该点放入一个栈内。
- 在原图的反图(即将所有
的有向边变成 )上进行 dfs 。从上一步中得到的栈的栈顶开始,每次遇到一个没有被遍历过的点,就遍历该点,此时该点所能到达的所有点即为一个强联通子图。
求答案:
由于每个点只能选 真或假 ,而同一个联通块中的所有点都是同样的值。所以当某个点的真和假都出现在同一个联通块中,则无解。随便选一个联通块,并将其中的点都作为答案就可以了。
但是这样还有一个问题,如下图:
此时我们无论选第一个联通块还是第二个联通块都是可行的,但是当我们再加上一条
这时根据上面我们求联通块的算法,若对于两个点之间只存在一条有向边
Code:
#include<cstdio> using namespace std; const int N=1e6+5; int n,m,tot,he[N*2],ne[N*2],to[N*2],he1[N*2],ne1[N*2],to1[N*2],d[N*2],ti,co[N*2];bool bj[N*2]; void add(int x,int y) { tot++;ne[tot]=he[x];to[tot]=y;he[x]=tot; ne1[tot]=he1[y];to1[tot]=x;he1[y]=tot; } void dfs1(int x) { int i,y; bj[x]=1; for(i=he[x];i!=0;i=ne[i]) { y=to[i]; if(bj[y]==0) dfs1(y); } d[++ti]=x; } void dfs2(int x) { int i,y; co[x]=ti;bj[x]=1; for(i=he1[x];i!=0;i=ne1[i]) { y=to1[i]; if(bj[y]==0) dfs2(y); } } void Kosaraju() { int i; for(i=1;i<=2*n;i++) { if(bj[i]==0) dfs1(i); } for(i=1;i<=n*2;i++) bj[i]=0; ti=0; for(i=n*2;i>=1;i--) { if(bj[d[i]]==0) { ti++;dfs2(d[i]); } } } int main() { int i,x,y,bj1,bj2; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d%d%d",&x,&bj1,&y,&bj2); add(x+n*bj1,y+n*(bj2^1)) ;add(y+n*bj2,x+n*(bj1^1)); } Kosaraju(); for(i=1;i<=n;i++) { if(co[i]==co[i+n]) { printf("IMPOSSIBLE"); return 0; } } printf("POSSIBLE\n"); for(i=1;i<=n;i++) { if(co[i]>co[i+n]) printf("1 "); else printf("0 "); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!