2-sat
\(2-sat\) 模型通过巧妙的连边将逻辑关系与图论相对应,通过形象化的图论模型来解决复杂关系问题
对于一个逻辑表达式“若 \(A\) 则 \(B\)”,可以通过 \(A\) 状态向 \(B\) 连一条边来表示
那么最终会形成一个有向图,图上一个点所能到达的所有点即是若选取 \(A\) 状态会随之发生的状态
\(2-sat\) 是应用这种思想来解决一个事物有两种状态的问题,实际操作时可以通过增加状态来解决一个事物有多个状态的问题
具体化地,设每个变量都有 \(0\) 和 \(1\) 两种状态
对于形如 \(x\) 取 \(op1\) 则 \(y\) 必须取 \(op2\) 的问题,可以从 \(x_{op1}\) 向 \(y_{op2}\) 连一条边
同时这个条件还意味着如果 \(y\) 取了 $ op1$ 则 \(x\) 必须取 \(op2\),那么从 \(y_{op1}\) 向 \(x_{op2}\) 连边
其他三种关系类似
从这里可以看出,\(2-sat\) 建出的边是两两对称的
对于建出的图,如果梳理出关系如果 \(x\) 取 \(0\) 则必须取 \(1\) 同时若 \(x\) 取 \(1\) 则必须取 \(0\) 则判为无解,其他均有解
而这里的判定等价于在同一个强联通分量中,有 \(tarjan\) 可以很方便地解决
对于输出方案,如果没有做任何要求,对于每个点选取拓扑序较大的那个
而强连通分量编号小正好对应了拓扑序大
而如果同时限制了字典序或求其他问题,则只好从每个点出发遍历一遍图了
放一道 模板题 的代码
代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+5;
const int maxm=4e4+5;
int n,m,x,y,hd[maxn],cnt,dfn[maxn],sta[maxn],tp,low[maxn],c[maxn],rev[maxn],num,tot;
bool vis[maxn];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Edge{
int nxt,to;
}edge[maxm];
void add(int u,int v){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
hd[u]=cnt;
return ;
}
void tarjan(int u){
dfn[u]=low[u]=++num;
sta[++tp]=u;vis[u]=true;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;tot++;
do{
v=sta[tp--];
c[v]=tot;
vis[v]=false;
}while(v!=u);
}
return ;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
rev[i*2-1]=i*2;
rev[i*2]=i*2-1;
}
for(int i=1;i<=m;i++){
x=read(),y=read();
add(x,rev[y]);
add(y,rev[x]);
}
for(int i=1;i<=n*2;i++){
if(!dfn[i])tarjan(i);
}
for(int i=1;i<=n*2;i++){
if(c[i]==c[rev[i]]){
puts("NIE");
return 0;
}
}
for(int i=1;i<=n*2;i+=2){
if(c[i]<c[rev[i]])printf("%d\n",i);
else printf("%d\n",rev[i]);
}
return 0;
}
模型变形
实际题目中通过复杂化背景与状态,或改变要求的量来增加难度
P3007 [USACO11JAN] The Continental Cowngress G
这道题要求出必须与非必须
如果两个状态有指向关系,那么只能选择被指向的,否则都可以
可以用 \(bitset\) 结合拓扑排序完成
由于 \(n\) 很小,可以直接 \(n^2\) 完成建图,然而题目要求方案数
可以发现其实在一种合法状态的基础上只能移动最多两个人
如果从 \(1\) 阵营同时移两个人到 \(2\) 阵营那么他们之间不符合题意
只有一种情况是交换两个阵营中的一对人达成目的
那么预处理出每个阵营中对面阵营使得其不能过去的人的个数,只有 \(0\) 和 \(1\) 可以过去
注意根据题意特判其中一个阵营走到没人的情况
首先乍一看状态是 \(n^2\) 的,可实际上只要当这个人需要成为别人条件的那些时间点有用,于是边点都变成 \(O(n)\) 的
可以发现限制条件的特殊性使得最终这张图是一个 \(DAG\)
求答案时只需要看图上其活着的状态能到达活着的状态数即可,可以用 \(bitset\) 来完成,对于空间问题采用分组的方式解决
乍一看每张图都有三种汽车是 \(3\) 种状态的做不了,但是观察发现除了 \(x\) 以外的地图只允许两种汽车,而 \(x\) 的个数很少,显然最后可以单独判断
那么对于限制条件模拟题意建图就好了
对于 \(x\) 的处理可以跑两次,分别将其当做 \(A\) 地图和 \(B\) 地图来对待,这样考虑到了所有车型,而复杂度允许 \(2^d\) 枚举
题意:定义一个组的 \(w\) 为组内两两矛盾值的最大值,求所有人分成两组的 \(w\) 和最小的情况
首先设 \(w(A)<w(B)\)
假如已知了 \(B\) 的最大值,那么就二分 \(w(A)\),同时可以列出多个 \(2-sat\) 的式子求解
考虑优化 \(n^2\) 枚举 \(B\) 最大值的过程
首先求出最大生成树
假如 \(B\) 的最大值不在生成树上
如果两端点距离超过了 \(2\),那么中间树上的边属于了 \(A\),不合法
否则都是距离为 \(2\) 的,特殊判断一下即可(即形成的集合就是黑白染色的集合)
至此,可能最大值在最大生成树上,降为 \(O(n)\)
优化建图
建图是图论的传统难题,自然也可以和 \(2-sat\) 有机结合在一起
对于边的限制很好体现,然而每组只有一个关键点的限制会产生大量连边,而这其中大部分都是冗余的
可以运用前缀优化建图的思想来解决
新增状态 \(pre(i,0)\) 表示这一组前 \(i\) 个都不是关键点
\(pre(i,1)\) 表示这一组前 \(i\) 个中出现了关键点
一个点成为关键点要求前 \(i-1\) 个没有关键点,且要求前 \(i\) 个有关键点
前 \(i-1\) 个有关键点要求前 \(i\) 个也有关键点,前 \(i\) 个没有关键点要求前 \(i-1\) 个也没有关键点
以上条件反之亦然
这样建出的图边点都是 \(O(n)\) 级别的
首先肯定要二分答案
把边的选与不选当做状态,点对周围边的制约当做上一道题中的组,然后一样去做就可以了
一样的道理,将区间 \([l,r]\) 拆分成前缀 \(l-1\) 和前缀 \(r\) 即可
当然,这一类题比较通用的方法也可以线段树优化建图
Summary
- 能否反应过来是 \(2-sat\) 问题关键在于寻找题目中隐藏的只有两种状态的变量
- 时刻考虑边点的复杂度,综合运用各种方法进行建图优化
- 观察建出图的性质,是否形成了 \(DAG\)