zoukankan      html  css  js  c++  java
  • 【模板】2-SAT问题

    问题简述

    (n) 个变量,每个变量可赋为 (1)(0)
    必须满足一些限制条件,如“ (a) 为1 或 (b) 为0 ” “ (a) 为0 且 (b) 为1” …
    判断是否有一种赋值方法满足所有限制条件,并构造答案。

    做法

    首先拆点,把每个变量 (i) 拆成 (i) 选1 和 (i) 选0 两个点,在这两个点中选出一个
    现在我们在 (2n) 个点中选出了 (n) 个点,相当于选择了一种赋值方案
    换句话说,每种赋值方案就是在 每对 (i) 选1 和 (i) 选0 中选一个点

    考虑所有限制条件,有两种类型。

    第一种是 “ (x_i=k_i)(x_j=k_j)
    我们可以连一些有向边 ((u,v)),表示如果选了 (u) ,就一定要选 (v)
    那针对这种情况,则要连 ((x_i选k_i, x_j选k_j))
    但这样还不够!
    回忆一下“逆否命题”,我们还需连 ((x_j不选k_j,x_i不选k_i)) 才可保证限制满足。

    第二种是 “ (x_i=k_i)(x_j=k_j)
    连边 ((x_i不选k_i, x_j选k_j))((x_j不选k_j,x_i选k_i))

    这样下来,我们发现连的所有边是对称的!(真命题&逆否命题嘛)
    接下来,我们对这个有向图跑强连通分量缩点。
    对每个强连通分量,要么都选,要么都不选。

    还记得如何选赋值方案吗?
    每对(i) 选1 和 (i) 选0 中只能选1个,那么判断一下,如果这两个点在同一个强连通分量中,则无解。
    如果不在,选拓扑排序中拓扑序靠后的那个点,(也就是 (tarjan) 中编号较小的那个点),可以保证这样选出的点是一组合理的赋值方案。

    代码

    洛谷模板题 (P4782)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 2000005;
    
    struct node{
        int v;
        node *nxt;
    }pool[N],*h[N];
    int cnt;
    void addedge(int u,int v){
        node *p=&pool[++cnt];
        p->v=v;p->nxt=h[u];h[u]=p;
    }
    
    int n,m;
    
    int tot;
    int dfn[N],low[N],scc,belong[N];
    int st[N],top,vis[N];
    void tarjan(int u){
        int v;
        dfn[u]=low[u]=++tot;
        vis[u]=1;
        st[top++]=u;
        for(node *p=h[u];p;p=p->nxt)
            if(!dfn[v=p->v]){
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
        if(low[u]==dfn[u]){
            scc++;
            while(1){
                belong[st[--top]]=scc;
                vis[st[top]]=0;
                if(st[top]==u) break;
            }
        }
    }
    
    
    int main()
    {
        int a,b,c,d;
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d%d",&a,&b,&c,&d);
            addedge(a+(1-b)*n,c+d*n);
            addedge(c+(1-d)*n,a+b*n);
        }
        
        for(int i=1;i<=2*n;i++)
            if(!dfn[i]) tarjan(i);
        
        int flag=0;
        for(int i=1;i<=n;i++)
            if(belong[i]==belong[i+n]) flag=1;
        if(flag) { printf("IMPOSSIBLE
    "); return 0; }
        printf("POSSIBLE
    %d",belong[1]>belong[n+1]);
        for(int i=2;i<=n;i++) printf(" %d",belong[i]>belong[n+i]);
        
        return 0;
    }
    
    既然选择了远方,便只顾风雨兼程
  • 相关阅读:
    SQL语句建表、设置主键、外键、check、default、unique约束
    郝斌老师的SQL教程
    Hello WPF!
    指针
    穷举法破解 zebrone1.1
    一条汇编指令引发的 蝴蝶效应 (后记)
    浮点 汇编指令基础知识
    收敛数列极限唯一性证明
    MFC SkinMagic使用方法
    Python初学1
  • 原文地址:https://www.cnblogs.com/lindalee/p/10992621.html
Copyright © 2011-2022 走看看