Skip to content

Latest commit

 

History

History
86 lines (79 loc) · 3.3 KB

File metadata and controls

86 lines (79 loc) · 3.3 KB

在实际问题中,2-SAT问题在大多数时候表现成以下形式:有N对物品,每对物品中必须选取一个,也只能选取一个,并且它们之间存在某些限制关系(如某两个物品不能都选,某两个物品不能都不选,某两个物品必须且只能选一个,某个物品必选)等,这时,可以将每对物品当成一个布尔值(选取第一个物品相当于0,选取第二个相当于1),如果所有的限制关系最多只对两个物品进行限制,则它们都可以转化成9种基本限制关系,从而转化为2-SAT模型。

【建模】 其实2-SAT问题的建模是和实际问题非常相似的。 建立一个2N阶的有向图,其中的点分为N对,每对点表示布尔序列A的一个元素的0、1取值(以下将代表A[i]的0取值的点称为i,代表A[i]的1取值的点称为i')。显然每对点必须且只能选取一个。然后,图中的边具有特定含义。若图中存在边<i, j>,则表示若选了i必须选j。可以发现,上面的9种限制关系中,后7种二元限制关系都可以用连边实现,比如NOT(A[x] AND A[y])需要连两条边<x, y'>和<y, x'>,A[x] OR A[y]需要连两条边<x', y>和<y', x>。而前两种一元关系,对于A[x](即x必选),可以通过连边<x', x>来实现,而NOT A[x](即x不能选),可以通过连边<x, x'>来实现。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
const int MAXN = 200007;
const int MAXM = 400005; //边的最大数
struct Edge {
    int to, next;
} edge[MAXM];
int head[MAXN], tot;
void init() {  //建图前初始化
    tot = 0;
    memset(head, -1, sizeof(head));
}
//u<<1表示u成立,u<<1|1表示u不成立
void add(int u, int v) {   //若事件u成立则v成立,注意结点下标要从0开始!
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
bool vis[MAXN];   //染色标记,为true表示选择,打印时用
int S[MAXN], top; //
bool dfs(int u) {
    if (vis[u ^ 1])
        return false;
    if (vis[u])
        return true;
    vis[u] = true;
    S[top++] = u;
    for (int i = head[u]; i != -1; i = edge[i].next)
        if (!dfs(edge[i].to))
            return false;
    return true;
}
 
bool Twosat(int n) {  //判断可行性
    memset(vis, false, sizeof(vis));
    for (int i = 0; i < n; i += 2) {
        if (vis[i] || vis[i ^ 1]) 
            continue;
        top = 0;
        if (!dfs(i)) {
            while (top)
                vis[S[--top]] = false;
            if (!dfs(i ^ 1))
                return false;
        }
    }
    return true;
}
 
int main() {
    init();
    int n=3; //事件总数
    int a=0,b=1,c=2; //3个事件的编号
    add(a<<1,b<<1); //如果a发生,那么b发生
    add(a<<1|1,b<<1); //如果a不发生,那么b发生
    add(b<<1,c<<1|1);  //如果b发生,那么c不发生
    if(Twosat(2*n)){  //1个事件分成了发生和不发生,所以乘2
        cout<<"Yes\n";
        for(int i=0;i<2*n;i+=2){ //vis[i]和vis[i+1]只能有一个为1
            if(vis[i])           //前者为1代表i/2发生,后者为1代表i/2不发生
                cout<<i/2<<' ';
        }
    }else{
        cout<<"No\n";
    }
    return 0;
}