2-SAT算法学习笔记

2-SAT:

好像大概是这么一个东西,有一些集合,每个集合中有两个元素 (ai,bi) ( a i , b i ) ,然后要求你从每一个集合中选出一个元素,但是同时对于集合元素的选取是有一些限制的,比如说什么选了 ai a i aj a j 必须选一个,或者选了 ai a i 就必须选 bj b j 之类的。
然后我们发现上面所有的限制条件都可以转化为一类,即选了 ai a i 就必须选择 bj b j 这种。
然后解决这种问题算法就是建立一个有向图,对于选 ai a i 就必须选 bj b j 这种限制,我们在图中从 ai a i 连向 bj b j ,同时我们发现如果选 aj a j 也必须选 bi b i ,所以在图中从 aj a j 连向 bi b i 。不难发现这个图有对称性。
对于这个有向图,如果从 ai a i 可以到达 bi b i ,那么 ai a i 一定是不可以选的,那么根据有向图的传递性以及这个图本身的对称性,那么我们发现只有当 ai a i bi b i 在同一强连通分量中是才有可能是无解的。
于是我们可以把图建出来之后缩强连通分量,然后判断一下有没有无解的情况,如果有解的话就直接按照拓扑序的逆序选择每个集合内的元素。
洛谷模板:

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a;i<=b;++i)
typedef long long ll;

using namespace std;

void File(){
    freopen("luogu4782.in","r",stdin);
    freopen("luogu4782.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(mul=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=1e6+10;
int n,m;
int beg[maxn<<1],to[maxn<<1],las[maxn<<1],cnte=1;
int bel[maxn<<1],cnt_scc,low[maxn<<1],dfn[maxn<<1],cnt_dfn;
stack<int>stk;

void add(int u,int v){las[++cnte]=beg[u];beg[u]=cnte;to[cnte]=v;}

void tarjan(int u){
    low[u]=dfn[u]=++cnt_dfn;
    stk.push(u);
    for(int i=beg[u];i;i=las[i]){
        if(!dfn[to[i]]){
            tarjan(to[i]);
            low[u]=min(low[u],low[to[i]]);
        }
        else if(!bel[to[i]])low[u]=min(low[u],dfn[to[i]]);
    }
    if(dfn[u]==low[u]){
        ++cnt_scc;
        for(int p;p!=u;stk.pop()){
            p=stk.top();
            bel[p]=cnt_scc;
        }
    }
}

void init(){
    read(n); read(m);
    int x,y,t1,t2;
    REP(i,1,m){
        read(x); read(t1); read(y); read(t2);
        add(x+(1-t1)*n,y+t2*n);
        add(y+(1-t2)*n,x+t1*n);
    }
}

void work(){
    REP(i,1,(n<<1))if(!dfn[i])
        tarjan(i);
    REP(i,1,n)if(bel[i]==bel[i+n]){
        puts("IMPOSSIBLE");
        return;
    }
    puts("POSSIBLE");
    REP(i,1,n){
        if(bel[i]<bel[i+n])putchar('0');
        else putchar('1');
        putchar(' ');
    }
}

int main(){
    File();
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值