2-SAT学习笔记

2-SAT学习笔记

问题

由数理逻辑的知识我们知道,任何一个合式公式对应于一个合取范式。判断一般合式公式的可满足性是一个NPC问题,甚至当存在一些项由三个或以上原子命题时,判断其可满足性仍然是NPC。

考虑一个每一项都只有至多两个原子命题的合取范式(2-SAT),其中要支持 ¬,,,, ,问是否存在一种真值指派,使得合取范式为真。

2-SAT形如:

(pq)(ab)(¬c)


一些简化

由于 ¬ 是“直观的”,我们考虑用这两种联结词表示其他联结词。不难证明, {¬,} 构成一个联结词完备集。其中:

  1. ab=¬(¬a)b=¬ab
  2. ab=¬(¬(ab))=¬(¬a¬b)
  3. ab=(ab)(ba)

因而我们只需要考虑含有 ¬ 的2-SAT即可。


建图

我们考虑图论手段。将每个原子命题拆成为真和为假两个点,记作 i,¬i 。如果存在一个 ab ,则从a到b连一条有向边。特别的,当存在一个约束 ¬a¬b 时,应该加上 ba (容易证明)。一个重要的论断告诉我们:

定理: 一个2-SAT是不可满足的当且仅当 i ,使得 i,¬i 在同一个强连通分量内。

为了证明这个定理,我们需要先证明一个引理:当且仅当 ab ,a和b在同一个强联通分量内。证明需要对其间的链长做归纳,这里不再赘述。

定理的证明:命题的必要性( )是显然的。

充分性( )(反证法)。假设 i,¬(i) 不在一个强联通分量内。考虑所有的强联通分量 A1,A2,,Ak ,对于 i,¬i 分别位于 Aφ(i),Aφ(¬i) ,我们用一条无向边连接 Aφ(i),Aφ(¬i) 。这样则利用强联通分量建出一张图,这个图要么是二分图,要么不是二分图。

  1. 如果是二分图,将图黑白染色,即可以说明存在一种真值指派满足2-SAT(这是显然的)。
  2. 如果不是二分图,我们假设图中有一个奇环。任取一个元素 i ,则Aφ(i),Aφ(¬i)之间必有一个经过奇数个点(偶数条边)的路径。设建立这些路径的原子命题为 a1,a2,,a2k ,则有:

    • ia1
    • ¬a1a2
    • ¬a2a3
    • ¬a2k1a2k
    • ¬a2k¬i

    我们将上面的式子整理为:

    • ia1
    • a1¬a2
    • ¬a2a3
    • ¬a2k¬i

    也就是 i¬i ,根据引理, i,¬i 在同一个强连通分量内,矛盾。

计算

对于给定的2-SAT,我们可以建出这样的一张图,通过Tarjan判断强联通分量即可。

struct TwoSat {
    struct node {
        int to, next;
    } edge[MAXN*30];
    int n, head[MAXN], top = 0;
    int Not(int i)
    { return n+i; }
    void push(int i, int j)
    { ++top, edge[top] = (node){j, head[i]}, head[i] = top; }

    int dfn[MAXN], low[MAXN], stk[MAXN], stk_top, instk[MAXN];
    int gp[MAXN], gp_top, dfn_top;
    void init(int _n)
    {
        n = _n;
        memset(dfn, 0, sizeof dfn); memset(head, 0, sizeof head);
        memset(instk, 0, sizeof instk); memset(gp, 0, sizeof gp);
        gp_top = dfn_top = stk_top = top = 0;
    }

    void tarjan(int nd)
    {
        dfn[nd] = low[nd] = ++dfn_top, stk[++stk_top] = nd, instk[nd] = 1;
        for (int i = head[nd]; i; i = edge[i].next) {
            int to = edge[i].to;
            if (!dfn[to]) tarjan(to), low[nd] = min(low[nd], low[to]);
            else if (instk[to]) low[nd] = min(low[nd], dfn[to]);
        }
        if (dfn[nd] == low[nd]) {
            int now; ++gp_top;
            do {
                now = stk[stk_top--], gp[now] = gp_top, instk[now] = 0;
            } while (now != nd);
        }
    }

    bool work()
    {
        for (int i = 1; i <= n*2; i++)
            if (!dfn[i])
                tarjan(i);
        for (int i = 1; i <= n; i++)
            if (gp[i] == gp[i+n])
                return false;
        return true;
    }
} ;

构造一组解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值