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\) 结合拓扑排序完成


P3513 [POI2011]KON-Conspiracy

由于 \(n\) 很小,可以直接 \(n^2\) 完成建图,然而题目要求方案数
可以发现其实在一种合法状态的基础上只能移动最多两个人
如果从 \(1\) 阵营同时移两个人到 \(2\) 阵营那么他们之间不符合题意
只有一种情况是交换两个阵营中的一对人达成目的
那么预处理出每个阵营中对面阵营使得其不能过去的人的个数,只有 \(0\)\(1\) 可以过去
注意根据题意特判其中一个阵营走到没人的情况


P5332 [JSOI2019]精准预测

首先乍一看状态是 \(n^2\) 的,可实际上只要当这个人需要成为别人条件的那些时间点有用,于是边点都变成 \(O(n)\)
可以发现限制条件的特殊性使得最终这张图是一个 \(DAG\)
求答案时只需要看图上其活着的状态能到达活着的状态数即可,可以用 \(bitset\) 来完成,对于空间问题采用分组的方式解决


P3825 [NOI2017] 游戏

乍一看每张图都有三种汽车是 \(3\) 种状态的做不了,但是观察发现除了 \(x\) 以外的地图只允许两种汽车,而 \(x\) 的个数很少,显然最后可以单独判断
那么对于限制条件模拟题意建图就好了
对于 \(x\) 的处理可以跑两次,分别将其当做 \(A\) 地图和 \(B\) 地图来对待,这样考虑到了所有车型,而复杂度允许 \(2^d\) 枚举


C. 佛罗里达

题意:定义一个组的 \(w\) 为组内两两矛盾值的最大值,求所有人分成两组的 \(w\) 和最小的情况

首先设 \(w(A)<w(B)\)
假如已知了 \(B\) 的最大值,那么就二分 \(w(A)\),同时可以列出多个 \(2-sat\) 的式子求解
考虑优化 \(n^2\) 枚举 \(B\) 最大值的过程
首先求出最大生成树
假如 \(B\) 的最大值不在生成树上
如果两端点距离超过了 \(2\),那么中间树上的边属于了 \(A\),不合法
否则都是距离为 \(2\) 的,特殊判断一下即可(即形成的集合就是黑白染色的集合)
至此,可能最大值在最大生成树上,降为 \(O(n)\)


优化建图

建图是图论的传统难题,自然也可以和 \(2-sat\) 有机结合在一起


P6378 [PA2010] Riddle

对于边的限制很好体现,然而每组只有一个关键点的限制会产生大量连边,而这其中大部分都是冗余的
可以运用前缀优化建图的思想来解决
新增状态 \(pre(i,0)\) 表示这一组前 \(i\) 个都不是关键点
\(pre(i,1)\) 表示这一组前 \(i\) 个中出现了关键点
一个点成为关键点要求前 \(i-1\) 个没有关键点,且要求前 \(i\) 个有关键点
\(i-1\) 个有关键点要求前 \(i\) 个也有关键点,前 \(i\) 个没有关键点要求前 \(i-1\) 个也没有关键点
以上条件反之亦然
这样建出的图边点都是 \(O(n)\) 级别的


CF587D Duff in Mafia

首先肯定要二分答案
把边的选与不选当做状态,点对周围边的制约当做上一道题中的组,然后一样去做就可以了


CF1215F Radio Stations

一样的道理,将区间 \([l,r]\) 拆分成前缀 \(l-1\) 和前缀 \(r\) 即可

当然,这一类题比较通用的方法也可以线段树优化建图


Summary

  • 能否反应过来是 \(2-sat\) 问题关键在于寻找题目中隐藏的只有两种状态的变量
  • 时刻考虑边点的复杂度,综合运用各种方法进行建图优化
  • 观察建出图的性质,是否形成了 \(DAG\)
posted @ 2021-12-21 20:35  y_cx  阅读(75)  评论(0编辑  收藏  举报