2-SAT 略解
2-SAT 问题
洛谷博客 github vercel
k-SAT
首先引入 k-SAT 问题。
k-SAT 问题说的就是有
比如
是一个 3-SAT 问题,它的一个解为
可以证明 k-SAT 在
2-SAT
2-SAT 问题是 k-SAT 问题在
每一个约束条件都可以表示为 「2-SAT 问题,只要会求解 2-SAT,就可以解决许多问题。建模当然比原算法难哈哈哈
接下来是重点:如何求解 2-SAT 问题。
1. 图论建膜
先建立膜型 stO CYB Orz
我们把每个变量拆成两个点,一个表示该变量为真,另一个表示该变量为假。我们约定
边表示蕴含关系,即
则原问题可化为:在约束对应的图上,选择
2. 求解
怎么解呢?当然要请出我们的 爆搜 Tarjan 大佬。
首先我们发现,如果一个节点与它的非节点互相连通,那么问题肯定无解,因为如果
然后我们发现,如果
如果
因此我们考虑将图用 tarjan 把强连通分量缩点,得到一张 DAG。如果一个点和它的非节点在同一个强连通分量中,则问题无解,否则,
于是,我们得到了一组可行解。
code
// by juruo999
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
namespace sat{
const int maxn=1000006;
vector<int> e[maxn*2];
int n;
inline int rho(int i,bool x){ // <i,x>
return i*2-x;
}
void add(int i,bool a,int j,bool b){ // x[i]==a => x[j]==b
e[rho(i,a)].push_back(rho(j,b));
e[rho(j,b^1)].push_back(rho(i,a^1));
}
int dfn[maxn*2],low[maxn*2],vis[maxn*2],cnt=0;
int scc[maxn*2],sccn=0,stk[maxn*2],tp=0;
void dfs(int u){
low[u]=dfn[u]=++cnt;
stk[++tp]=u;
vis[u]=1;
for(int i=e[u].size()-1;i>=0;i--){
int v=e[u][i];
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
sccn++;
while(stk[tp]!=u){
vis[stk[tp]]=0;
scc[stk[tp]]=sccn;
tp--;
}
vis[stk[tp]]=0;
scc[stk[tp]]=sccn;
tp--;
}
}
bool x[maxn];
bool solve(){
for(int i=1;i<=2*n;i++){
if(!dfn[i]){ dfs(i); }
}
for(int i=1;i<=n;i++){
if(scc[rho(i,0)]==scc[rho(i,1)]){
return 0;
}else{
x[i]=scc[rho(i,0)]>scc[rho(i,1)];
}
}
return 1;
}
bool& get(int i){
return x[i];
}
}
using sat::e;
using sat::rho;
int n,m;
int main(){
scanf("%d%d",&n,&m);
sat::n=n;
for(int i=1;i<=m;i++){
int x,a,y,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
sat::add(x,a^1,y,b);
}
if(sat::solve()){
puts("POSSIBLE");
for(int i=1;i<=n;i++){
printf("%d%c",sat::x[i]," \n"[i==n]);
}
}else{
puts("IMPOSSIBLE");
}
return 0;
}
例题
模板题
双倍经验
NOI2017 游戏
第三题有一些难度,我做这道题的时候一开始建模建错了,结果不能保证每个地图都能分配到赛车……