2-sat入門(例題hdu1814,poj3648)持續更新

2-sat問題描述

2-sat問題是這樣的:有n個布爾變量xi,另有m個需要滿足的條件,每個條件的形式都是“xi爲真/假或者xj爲真/假”。比如“x1爲真或者x2爲假”。注意這裏的“或”是指兩個條件至少一個是正確的。

由於在2-SAT問題中,最多隻對兩個元素進行限制,所以可能的限制關係共有11種:
A[x] (1)
NOT A[x] (2)
A[x] AND A[y]
A[x] AND NOT A[y]
A[x] OR A[y] (3)
A[x] OR NOT A[y] (4)
NOT (A[x] AND A[y]) (5)
NOT (A[x] OR A[y]) (6)
A[x] XOR A[y] (7)
NOT (A[x] XOR A[y]) (8)
A[x] XOR NOT A[y] (9)

進一步,A[x] AND A[y]相當於(A[x]) AND (A[y])(也就是可以拆分成A[x]與A[y]兩個限制關係),NOT(A[x] OR A[y])相當於NOT A[x] AND NOT A[y](也就是可以拆分成NOT A[x]與NOT A[y]兩個限制關係)。因此,可能的限制關係最多隻有9種。

比如A[x]or B[x],那麼如果!A[x]就必然有B[x],所以從!A[x]連一條有向邊連向B[x],同理如果!B[x]就必然有A[x],所以從!B[x]連一條有向邊連向A[x]。這裏的有向邊可以理解爲推導出。
對於單個的A[x],就相當於A[x]||A[x],從!A[x]連向A[x]即可。

於是可以構造有向圖G,G包含2n個頂點,代表2n個元素。則將問題轉化爲從G中選出n個頂點,並且ai和(~ai)的頂點不能同時被選,使其滿足2-sat的條件。

染色法

逐一考慮每個沒有賦值的變量,設爲xi。我們先假定它是假的,然後標記結點2i,並且沿着有向邊標記所有能標記的結點。如果標記過程中發現某個變量對應的兩個結點都被標記,則“xi爲假”這個假定不成立,需要改爲“xi爲真”,然後重新標記。注意,這個算法沒有回溯過程。如果當前考慮的變量不管賦值是真還是假都會引起矛盾,可以證明整個2-sat問題無解
雖然這個算法複雜度爲O(n*m),但是它的一個好處就是可以求字典序

hdu1814

Peaceful Commission

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=20020;
const int MAXM=100010;
struct _Edge
{
    int to,next;
}edge[MAXM];
int hd[MAXN],tot;
void inti(void)
{
    tot=0;
    msc(hd);
}
void addedge(int u,int v)
{
    edge[tot].to=v;
    edge[tot].next=hd[u];
    hd[u]=tot++;
}
bool vs[MAXN];//染色標記,true表示選擇
int Stack[MAXN],top;
bool dfs(int u)
{
    if(vs[u^1]) return false;
    if(vs[u]) return true;
    vs[u]=true;
    Stack[top++]=u;
    for(int i=hd[u];i!=-1;i=edge[i].next)
        if(!dfs(edge[i].to)) return false;
    return true;
}
bool twosat(int n)
{
    ms(vs);
    for(int i=0;i<n;i+=2)
    {
        if(vs[i]||vs[i^1]) continue;//可選擇
        top=0;
        if(!dfs(i)){
            while(top) vs[Stack[--top]]=false;
            if(!dfs(i^1)) return false;//如果i和i^1都不能選,無解
        }
    }
    return true;
}
int main(int argc, char const *argv[])
{
    int n,m;
    while(scanf("%d %d",&n,&m)==2){
        inti();
        while(m--){
            int a,b;
            scanf("%d %d",&a,&b);
            a--,b--;
            addedge(a,b^1);
            addedge(b,a^1);
        }
        if(twosat(2*n)){
            for(int i=0;i<2*n;i++)
                if(vs[i]) printf("%d\n",i+1 );
        }
        else puts("NIE");
    }
    return 0;
}

強連通+拓撲排序

圖建好,可以求出G的強連通分量,如果選中強連通分量的一個點,那麼就必須選中強連通分量的所有點。如果ai和~ai在一個強連通分量中,則ai和~ai就必須都選中,矛盾,故此時2-sat問題無解。 如果沒有產生矛盾,就將強連通分量進行縮點,縮點後重新建反向圖G*。由於 G *中沒有環,所以它有拓撲結構。按拓撲順序將點進行染色。
算法複雜度O(m),但不能求字典序,只能給出一個任意解。

poj3648

Wedding

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=1010;
const int MAXM=100010;
struct _Edge
{
    int to,next;
}edge[MAXM];
int hd[MAXN],tot;
void inti(void)
{
    tot=0;
    msc(hd);
}
void addedge(int u,int v)
{
    edge[tot].to=v;
    edge[tot].next=hd[u];
    hd[u]=tot++;
}
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];
int Index,top;
int scc;
bool Instack[MAXN];
void tarjan(int u)
{
    int v;
    Low[u]=DFN[u]=++Index;
    Stack[top++]=u;
    Instack[u]=true;
    for(int i=hd[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].to;
        if(!DFN[v]){
            tarjan(v);
            if(Low[u]>Low[v])
                Low[u]=Low[v];
        }
        else if(Instack[v]&&Low[u]>DFN[v])
            Low[u]=DFN[v];
    }
    if(Low[u]==DFN[u]){
        scc++;
        do{
            v=Stack[--top];
            Instack[v]=false;
            Belong[v]=scc;
        }while(v!=u);
    }
}
bool solvable(int n)//n是總個數,需要選擇一半
{
    ms(DFN);
    ms(Instack);
    Index=scc=top=0;
    for(int i=0;i<n;i++)
        if(!DFN[i]) tarjan(i);
    for(int i=0;i<n;i+=2)
        if(Belong[i]==Belong[i^1])
            return false;
    return true;
}
queue<int > q;
vector<vector<int > >dag;//縮點後的逆圖
char color[MAXN];
int indeg[MAXN];
int cf[MAXN];
void solve(int n)
{
    dag.assign(scc+1,vector<int> ());
    ms(indeg);
    ms(color);
    for(int u=0;u<n;u++)
        for(int i=hd[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(Belong[u]!=Belong[v])
            {
                dag[Belong[v]].push_back(Belong[u]);//反向邊
                indeg[Belong[u]]++;
            }
        }
    for(int u=0;u<n;u+=2)
    {
        cf[Belong[u]]=Belong[u^1];
        cf[Belong[u^1]]=Belong[u];
    }
    while(!q.empty()) q.pop();
    for(int i=1;i<=scc;i++)
        if(indeg[i]==0)
            q.push(i);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        if(color[u]==0){
            color[u]='R';
            color[cf[u]]='B';
        }
        int sz=dag[u].size();
        for(int i=0;i<sz;i++)
        {
            indeg[dag[u][i]]--;
            if(indeg[dag[u][i]]==0)
                q.push(dag[u][i]);
        }
    }
}
int change(char *s)
{
    int num=0,i=0;
    while('0'<=s[i]&&s[i]<='9')
        num=num*10+s[i++]-'0';
    if(s[i]=='w') return num<<1;
    return num<<1|1;
}
int main(int argc, char const *argv[])
{
    int n,m;
    while(scanf("%d %d",&n,&m)==2&&(n||m)){
        inti();
        while(m--){
            char ss[4],ss2[4];
            scanf("%s%s",ss,ss2);
            int u=change(ss),v=change(ss2);
            addedge(u^1,v);
            addedge(v^1,u);
        }
        addedge(1,0);//0號一定去,因爲求的是和新娘坐在一邊的
        if(solvable(2*n)){
            solve(2*n);
            for(int i=1;i<n;i++)
                {
                    if(color[Belong[i<<1]]=='R') printf("%dw",i );
                    else printf("%dh",i );
                    printf("%c",i==n-1?'\n':' ' );
                }
        }
        else puts("bad luck");
    }
    return 0;
}
發佈了110 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章