zoukankan      html  css  js  c++  java
  • 2-sat 学习笔记

    一、问题描述

    以你咕的模板题为例

    题目描述

    (n)个布尔变量(x_1)~(x_n),另有(m)个需要满足的条件,每个条件的形式都是“(x_i)为true/false或(x_j)为true/false”。

    比如“(x_1)为真或(x_3)为假”、“(x_7)为假或(x_2)为假”。

    2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

    二、逻辑关系

    这里不用太过专业的符号来说明逻辑关系,仅仅是感性说明(没错就是我菜

    设条件为大写字母A,B,非A,非B只条件的另一种取值

    A或者B 等价于 非A则B 和 非B则A

    注意到后面两个要求似乎有蜜汁对称性,而且它们都是有向的

    根据后一句话,我们借助并查集扩展域的思想,把一个点的两个取值拆开,进行建图

    三、建图

    这里就画图举例说明吧

    给出这样三个条件((1,1))((2,1)),((2,1))((3,1)),((2,0))或(3,1)$

    ((x,y))表示(x)需要取值(y)

    我们把点(x)取值(0)的点编号(x),取值(1)的点编号(x+n)

    我们可以建出如下的图

    有向边((u,v))表示如果(u)选,(v)一定选

    很显然这是有解的,我们可以去(1,2,3)等点集

    怎么样才会无解呢?

    如果一个点的出边的点构成的集合中又出现了它自己?这样可以不选它而选另一个值啊

    如果这个集合出现了另一个值呢?

    好吧,这里的无解其实就是综合一下,选它的同时要选它的另一个值,选它的另一个值同时要选它

    等价于两者在有向图中处于同一个环中

    这样我们就可以通过tarjan求环来判断有解性了

    四、构造合法方案

    如果一个点没有伸出去的边,就代表它不约束别人,我们可以随便选择选或不选它

    (cho[]),若(cho[x]==0)则代表我们选择了它,否则我们没有选择它

    优先选择出度为0的点

    我们可以在缩点后的图中反向建图,然后做拓扑排序

    注意我们选择一个点后,要把它对应的点置不选

    事实上,我们也可以没必要做这么麻烦

    dfs遍历的DAG图的序列反过来其实就是反向建边的拓扑序

    在tarjan中先做的点的编号就是反向建边拓扑序

    那么我们直接比编号大小进行赋值就可以啦

    五、参考代码

    #include <cstdio>
    int min(int x,int y){return x<y?x:y;}
    const int N=2e6+10;
    int head[N],to[N<<1],Next[N<<1],cnt,n,m;
    void add(int u,int v)
    {
        to[++cnt]=v;Next[cnt]=head[u];head[u]=cnt;
    }
    void init()
    {
        scanf("%d%d",&n,&m);
        for(int i,a,j,b,k=1;k<=m;k++)
        {
            scanf("%d%d%d%d",&i,&a,&j,&b);
            add(i+(1-a)*n,j+b*n),add(j+(1-b)*n,i+a*n);
        }
    }
    int dfn[N],low[N],in[N],s[N],ha[N],tot,dfs_clock,n_;
    void tarjan(int now)
    {
        dfn[now]=low[now]=++dfs_clock;
        s[++tot]=now;
        in[now]=1;
        for(int i=head[now];i;i=Next[i])
        {
            int v=to[i];
            if(!dfn[v])
            {
                tarjan(v);
                low[now]=min(low[now],low[v]);
            }
            else if(in[v])
                low[now]=min(low[now],dfn[v]);
        }
        if(low[now]==dfn[now])
        {
            int k;++n_;
            do
            {
                k=s[tot--];
                ha[k]=n_;
                in[k]=0;
            }while(k!=now);
        }
    }
    void work()
    {
        for(int i=1;i<=n<<1;i++) if(!dfn[i]) tarjan(i);
        for(int i=1;i<=n;i++)
            if(ha[i]==ha[i+n])
            {
                printf("IMPOSSIBLE
    ");
                return;
            }
        printf("POSSIBLE
    ");
        for(int i=1;i<=n;i++)
            printf("%d ",ha[i]>ha[i+n]);
    }
    int main()
    {
        init();
        work();
        return 0;
    }
    
    

    2018.9.1

  • 相关阅读:
    小程序,计算一个字符串中每个字符出现的次数
    打印好看的整齐的清单
    输入符号,宽,高,打印此符号组成的矩形
    输入名字显示其生日,没有则让输入生日,做记录
    51ll网产品信息保存为txt文件
    .py文件 改成默认用idle打开
    协程原理代码演示
    python: ImportError: cannot import name 'Style' from 'openpyxl.styles' 解决方法
    python编程快速上手第7章习题20
    HDU 1010 Tempter of the Bone
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9570894.html
Copyright © 2011-2022 走看看