LGP4782 [LG TPLT] 2-SAT 学习笔记
LGP4782 [LG TPLT] 2-SAT 学习笔记
前言
为什么这个时候才把这个板子过了呢?
……大概是因为以前题解的代码长得太难看了?
……这是借口吗?
题意简述
有 \(n\) 个布尔变量与 \(m\) 个需要满足的条件。每个条件的形式都形如 \(x_i=a_k\lor x_j=b_k\)。其中 \(a_k\) 和 \(b_k\) 取值为 \(0\) 或 \(1\)。
问能否给出使得一组方案满足所有要求。若可以,给出一组方案。
做法解析
解决 \(\texttt{2-SAT}\) 问题应用了图论建模的思想。
\(x_i=a_k\lor x_j=b_k\) 可以写成:“若 \(x_i\) 不满足 \(a_k\) 则 \(x_j\) 必须满足 \(b_k\),若 \(x_j\) 不满足 \(b_k\) 则 \(x_i\) 必须满足 \(a_k\)”。我们把问题放到图论上,令结点 \(i\) 表示 \(x_i=1\) 的情况,结点 \(i+n\) 表示 \(x_i=0\) 的情况,于是我们就可以连出两条边:\(i_{\neg a_k}\to j_{b_k}\)、\(j_{\neg b_k}\to i_{a_k}\)。然后跑强连通分量。
为什么是强连通分量呢?\(u\to v\) 意味着若 \(u\) 则 \(v\),也即 \(u\) 与 \(v\) 取值。我们对于每一对 \((i,i+n)\) 都需要“激活”其中的一个点(要么 \(i\) 为真成立,要么 \(i\) 为假成立),激活一个点后它能到达的所有点的真假也会与它一样。你处理完强连通分量之后若 \(x\) 和 \(x+n\) 处于同一强连通分量中即说明无解,因为无论你激活其中的哪个都避免不了另一个被激活。
至于输出方案我们要怎么做呢?代表取真的 \(i\) 和代表取假的 \(i+n\),哪个所处的强连通分量编号大我们就让 \(x_i\) 取哪边的值。为什么呢?我们知道塔扬算法里面,对强连通分量的标号是沿着 \(\texttt{dfs}\) 树的,标号越大说明越靠近搜索树的根部。所以对于两个强连通分量 \(a,b\),\(a>b\) 就意味着缩点之后 \(a\) 至少不是 \(b\) 的父亲。也就是说选 \(a\) 不会干涉到 \(b\)。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e6+5;
int N,M,X,Y,ya,yb,na,nb;
vector<int> Gr[MaxN<<1];
int dfn[MaxN<<1],low[MaxN<<1],stk[MaxN<<1],ktp;
int tot,ccnt,bel[MaxN<<1];
void tarjan(int u){
dfn[u]=low[u]=++tot,stk[++ktp]=u;
for(auto v : Gr[u]){
if(!dfn[v])tarjan(v),minner(low[u],low[v]);
else if(!bel[v])minner(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
ccnt++;
for(int v;ktp;){
v=stk[ktp--],bel[v]=ccnt;
if(v==u)break;
}
}
}
bool twosat(int n){
for(int i=1;i<=2*n;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++)if(bel[i]==bel[i+n])return false;
return true;
}
int main(){
readis(N,M);
for(int i=1;i<=M;i++){
readis(X,ya,Y,yb);
na=ya^1,nb=yb^1;
Gr[X+na*N].push_back(Y+yb*N);
Gr[Y+nb*N].push_back(X+ya*N);
}
if(twosat(N)){
puts("POSSIBLE");
for(int i=1;i<=N;i++)writip(bel[i]>bel[i+N]);
}
else puts("IMPOSSIBLE");
return 0;
}
浙公网安备 33010602011771号