P4782 【模板】2-SAT 问题
芝士
2-SAT问题就是一种给出n个变量,满足一些二元限制比如(x取1,y必须取0),要求求出n个变量赋值的合法方案的题目
3-SAT及更多是NP完全问题
2-SAT求解可以用tarjan,时间复杂度\(O(n+m)\),但是要求输出字典序最小解的时候只有\(O(nm)\)的算法
算法流程就是要拆点连边,x表示x取1,x+n表示x取0,然后用边表示“必须”的条件,<x,y+n>表示x取1,y必须取0,则如果选了x,相连的所有点都要选
所以处于同一强连通分量里的点必须都选,无解显然是x与x+n在一个强连通分量中
输出方案的时候,为了避免冲突和保证能构造出解,需要在缩点之后的DAG里找到拓扑序的反序小的点先选(就是对后面没有影响的,因为选了x之后后面的点都要选),tarjan之后的sccno正好是反向的拓扑序,所以优先选标号小的条件即可
思路
板子,转化成x为a1时y必须为b、y为b1时x必须为a即可
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
int u[2000100],v[2000100],fir[2000100],nxt[2000100],cnt,low[2000100],dfn[2000100],dfs_clock,vis[2000100],scc_cnt,sccno[2000100],n,m,choose[2000100];
stack<int> S;
void addedge(int ui,int vi){
++cnt;
u[cnt]=ui;
v[cnt]=vi;
nxt[cnt]=fir[ui];
fir[ui]=cnt;
}
void tarjan(int u){
low[u]=dfn[u]=++dfs_clock;
S.push(u);
vis[u]=true;
for(int i=fir[u];i;i=nxt[i]){
if(!dfn[v[i]]){
tarjan(v[i]);
low[u]=min(low[u],low[v[i]]);
}
else if(vis[v[i]])
low[u]=min(low[u],low[v[i]]);
}
if(low[u]==dfn[u]){
scc_cnt++;
while(1){
int x=S.top();
S.pop();
vis[x]=false;
sccno[x]=scc_cnt;
if(x==u)
break;
}
}
}
void get_scc(void){
for(int i=1;i<=2*n;i++)
if(!dfn[i])
tarjan(i);
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int xi,xj,a,b;
scanf("%d %d %d %d",&xi,&a,&xj,&b);
addedge(xi+(a^1)*n,xj+b*n);
addedge(xj+(b^1)*n,xi+a*n);
}
get_scc();
bool f=true;
for(int i=1;i<=n;i++){
if(sccno[i]==sccno[i+n]){
f=false;
break;
}
if(sccno[i]<sccno[i+n])
choose[i]=0;
else
choose[i]=1;
}
if(!f){
printf("IMPOSSIBLE\n");
}
else{
printf("POSSIBLE\n");
for(int i=1;i<=n;i++)
printf("%d ",choose[i]);
printf("\n");
}
return 0;
}