2-SAT 用于求解布尔方程组,其中每个方程最多含有两个变量,方程的形式为 \((a∨b)=1\) ,即式子 \(a\) 为真或式子 \(b\) 为真。求解的方法是根据逻辑关系式建图,然后求强联通子图,每一个强联通子图的答案都是一样的。
建图:
这里以模版题为例:
题意:给定若干个需要满足的条件,其形式为 \(a,1/0,b,1/0\) ,表示要求 \(a\) 为真/假 或 \(b\) 为真/假。
我们将一个点拆成两个 \(a\) 和 \(\neg a\) ,分别表示 \(a\) 取真和取假。然后根据方程式,建一个有向图,其中有向边 \(a\to b\) 的定义为:若 \(a\) 被满足,则 \(b\) 也要被满足。一共有四种可能的情况:
- 若 \(a\) 为真或 \(b\) 为真:则当 \(a\) 为假时,\(b\) 一定为真,将 \(\neg a\) 向 \(b\) 连一条有向边;当 \(b\) 为假时,\(a\) 一定为真,将 \(\neg b\) 向 \(a\) 连一条有向边。
- 若 \(a\) 为真或 \(b\) 为假:则当 \(a\) 为假时,\(b\) 一定为假,将 \(\neg a\) 向 \(\neg b\) 连一条有向边;当 \(b\) 为真时,\(a\) 一定为假,将 \(b\) 向 \(\neg a\) 连一条有向边。
- 若 \(a\) 为假或 \(b\) 为真:则当 \(a\) 为真时,\(b\) 一定为真,将 \(a\) 向 \(b\) 连一条有向边;当 \(b\) 为假时,\(a\) 一定为假,将 \(\neg b\) 向 \(\neg a\) 连一条有向边。
- 若 \(a\) 为假或 \(b\) 为假:则当 \(a\) 为真时,\(b\) 一定为假,将 \(a\) 向 \(\neg b\) 连一条有向边;当 \(b\) 为真时,\(a\) 一定为假,将 \(b\) 向 \(\neg a\) 连一条有向边。
注意连边建图时,一定要将所有的情况全部连起来,并且一定要满足 若 \(a\) 满足,则一定可以推倒 \(b\) 满足。
求强联通子图:
这里介绍 Kosaraju 算法。
该算法分为两步:
- 先对原图中任意一个点开始进行 dfs ,直到所有点都被访问过一遍。在 dfs 时,当我们每退出一个点时,就将该点放入一个栈内。
- 在原图的反图(即将所有 \(A\to B\) 的有向边变成 \(B\to A\) )上进行 dfs 。从上一步中得到的栈的栈顶开始,每次遇到一个没有被遍历过的点,就遍历该点,此时该点所能到达的所有点即为一个强联通子图。
求答案:
由于每个点只能选 真或假 ,而同一个联通块中的所有点都是同样的值。所以当某个点的真和假都出现在同一个联通块中,则无解。随便选一个联通块,并将其中的点都作为答案就可以了。
但是这样还有一个问题,如下图:
此时我们无论选第一个联通块还是第二个联通块都是可行的,但是当我们再加上一条 \(B\to !C\) 的边,同时也会加上新的一条 \(C\to !B\) 的边。这时我们就不能取联通块 \(A,B,C,D\) 了。
这时根据上面我们求联通块的算法,若对于两个点之间只存在一条有向边 \(a\to b\) ,我们在遍历反图时会先遍历 \(a\) ,此时边反向变成了 \(b\to a\) ,然后 \(a\) 就无法达到 \(b\) ,所以 \(b\) 所在的联通块的编号会大于 \(a\) 所在的联通块,这样我们通过取编号最大的那个联通块里的点就可以避免上面这种问题。
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;
}
标签:bj,一条,联通,int,neg,笔记,学习,tot,SAT
From: https://www.cnblogs.com/Cyanwind/p/18352102