LUOGU P4782 【模板】2-SAT 问题

题目背景

2-SAT 问题 模板
题目描述

有n个布尔变量 x1x_1x1​ ~ xnx_nxn​ ,另有m个需要满足的条件,每个条件的形式都是“ xix_ixi​ 为true/false或 xjx_jxj​ 为true/false”。比如“ x1x_1x1​ 为真或 x3x_3x3​ 为假”、“ x7x_7x7​ 为假或 x2x_2x2​ 为假”。2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入输出格式
输入格式:

第一行两个整数n和m,意义如体面所述。

接下来m行每行4个整数 i a j b,表示“ xix_ixi​ 为a或 xjx_jxj​ 为b”(a,b∈{0,1})

输出格式:

如无解,输出“IMPOSSIBLE”(不带引号); 否则输出”POSSIBLE”(不带引号),下 一行n个整数 x1x_1x1​ ~ xnx_nxn​ ( xix_ixi​ ∈{0,1}),表示构造出的解。

输入输出样例
输入样例#1:

3 1
1 1 3 0

输出样例#1:

POSSIBLE
0 0 0

解题思路

2-SAT 模板题,将i拆成i*2与i*2+1两个点。对于要么a,要么b的情况让a向b的另一个点连边,b向a的另一个点连边,然后tarjan求出强联通分量,在同一个强联通分量的必须选。如果a与a拆的点在同一个强联通分量里,就证明矛盾。输出方案时要利用tarjan后图的性质,是一个逆拓扑排序,然后对于一个点拆成的两个点选出拓扑序更大的。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>

using namespace std;
const int MAXN = 1e6+5;

inline int rd(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {f=ch=='-'?-1:1;ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int n,m,head[MAXN<<1],cnt;
int to[MAXN<<1],nxt[MAXN<<1];
int bl[MAXN<<1],col_num,stk[MAXN<<1],top;
int dfn[MAXN<<1],low[MAXN<<1],num;
bool vis[MAXN<<1],flag;

inline void add(int bg,int ed){
    to[++cnt]=ed,nxt[cnt]=head[bg],head[bg]=cnt;
}

void tarjan(int x){
    dfn[x]=low[x]=++num;
    stk[++top]=x;vis[x]=1;
    for(register int i=head[x];i;i=nxt[i]){
        int u=to[i];
        if(!dfn[u]) {
            tarjan(u);
            low[x]=min(low[x],low[u]);
        }
        else if(vis[u]) low[x]=min(low[x],dfn[u]);
    }
    if(low[x]==dfn[x]){
        bl[x]=++col_num;vis[x]=0;
        while(stk[top]!=x){
            vis[stk[top]]=0;
            bl[stk[top--]]=col_num;
        }top--;
    }
}

int main(){
    n=rd();m=rd();
    for(register int i=1;i<=m;i++){
        int x=rd(),sx=rd(),y=rd(),sy=rd();
        int xt=sx^1,yt=sy^1;
        add(x<<1|xt,y<<1|sy);
        add(y<<1|yt,x<<1|sx);
    }
    for(register int i=2;i<=(n<<1|1);i++) if(!dfn[i]) tarjan(i);
    for(register int i=1;i<=n;i++)
        if(bl[i<<1]==bl[i<<1|1]) {flag=1;break;}
    if(flag) puts("IMPOSSIBLE");
    else{
        puts("POSSIBLE");
        for(register int i=1;i<=n;i++)
            printf("%d ",bl[i<<1]>bl[i<<1|1]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值