P4782 【模板】2-SAT 问题

P4782 【模板】2-SAT 问题

将每个数的真假分成两个点$i+n,i$

$a$真或$b$假

$=a$假$b$必假,$b$真$a$必真

$=a->b,b+n->a+n$

以此类推建边

如果$x,x+n$在同一个强连通分量里,就不成立

用Tarjan判断。

注意有$2n$个点,每个都要跑Tarjan

询问可行做法时,我们总是取$x,x+n$中拓扑序靠后的那个

也就是连通块编号小的(因为是dfs)

连边时用的是位运算

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=2000003;
vector <int> g[N];
int n,m,dfn[N],low[N],K,be[N],B,st[N],tp;
void Tar(int x){
    dfn[x]=low[x]=++K; st[++tp]=x;
    for(int i:g[x]){
        if(!dfn[i]) Tar(i),low[x]=min(low[x],low[i]);
        else if(!be[i]) low[x]=min(low[x],dfn[i]);
    }
    if(dfn[x]==low[x]){
        be[x]=++B;
        for(;st[tp]!=x;--tp) be[st[tp]]=B;
        --tp;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,q,w,e,r;i<=m;++i){
        scanf("%d%d%d%d",&q,&w,&e,&r);
        g[q+n*(w^1)].push_back(e+n*r);
        g[e+n*(r^1)].push_back(q+n*w);
    }
    for(int i=1;i<=n*2;++i) if(!dfn[i]) Tar(i);
    for(int i=1;i<=n;++i)
        if(be[i]==be[i+n]){
            puts("IMPOSSIBLE");
            return 0;
        }
    puts("POSSIBLE");
    for(int i=1;i<=n;++i)
        printf("%d ",be[i]>be[i+n]);
    return 0;
}

 

posted @ 2021-07-25 18:05  kafuuchino  阅读(48)  评论(0编辑  收藏  举报