【模板】2-sat問題

部分內容轉自luogu神犇題解:https://www.luogu.org/problemnew/solution/P4782
2-SAT定義:

即給定一些布爾方程,每個變量只能爲truetrue or falsefalse
要求對這些變量進行賦值,滿足布爾方程。
簡單來說就是離散數學中的布爾方程例如: (¬ab¬c)(ab¬c)(¬a¬bc)(¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c)
現在要做的是,爲 ABC 三個變量賦值,滿足三位學生的要求。
但這是 SAT 問題,已被證明爲 NP。
那麼 2-SAT 是什麼呢?
2-SAT,即只有兩個條件,例如布爾方程當中的 c,¬cc,\neg c 沒了,變成了這個樣子:(¬ab)(ab)(¬a¬b)(\neg a\vee b) \wedge (a\vee b) \wedge (\neg a\vee\neg b)

建圖:
xxyy至少選一個:(x,0,y,1),(y,0,x,1)(x,0,y,1),(y,0,x,1)
xxyy不能同時選:(x,1,y,0),(y,1,x,0)(x,1,y,0),(y,1,x,0)
選了xx就必須選yy(x,1,y,1),(y,0,x,0)(x,1,y,1),(y,0,x,0)

那麼如何解決2-sat問題呢?

用圖論中的強連通分量
對於每個變量 xx,我們建立兩個點:¬x¬x 分別表示變量 xx 取 true 和取 false。所以,圖的節點個數是兩倍的變量個數。在存儲方式上,可以給第 ii 個變量標號爲 ii,其對應的反值標號爲 i+ni + n。對於每個同學的要求(ab)(a∨b),轉換爲 ¬ab¬ba¬a→b∧¬b→a。對於這個式子,可以理解爲:「若 a 假則 b 必真,若 b 假則 a 必真」然後按照箭頭的方向建有向邊就好了。綜上,我們這樣對上面的方程建圖.

同一強連通分量內的變量值一定是相等的。也就是說,如果 x 與 ¬x¬x 在同一強連通分量內部,一定無解。反之,就一定有解了。
但是,對於一組布爾方程,可能會有多組解同時成立。要怎樣判斷給每個布爾變量賦的值是否恰好構成一組解呢?

這個很簡單,只需要 當 xx 所在的強連通分量的拓撲序在 ¬x¬x 所在的強連通分量的拓撲序之後取 xx 爲真 就可以了。在使用 Tarjan 算法縮點找強連通分量的過程中,已經爲每組強連通分量標記好順序了——不過是反着的拓撲序。所以一定要寫成 color[x]<color[x]color[x] < color[-x]

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e6+5;
int dfn[maxn<<1],low[maxn<<1],sta[maxn<<1],vis[maxn<<1],dfnnum;
int stalen;
int n;
int tag[maxn<<1];
struct Edge{
	int u,v,nxt;
}edge[maxn<<1];
int head[maxn<<1],tot;
inline void init(){
	memset(head,-1,sizeof(head));
	tot = 0;
}
inline void addedge(int u,int v){
	edge[++tot] = {u,v,head[u]};
	head[u] = tot;
}
inline void checkadd(int i,int a,int j,int b){
	addedge(i+n*(a&1),j+n*(b^1));
	addedge(j+n*(b&1),i+n*(a^1));
}
int num;
void tarjan(int u){
	dfn[u] = low[u] = ++dfnnum;
	sta[stalen++] = u,vis[u] = 1;
	for(int i = head[u]; ~i; i = edge[i].nxt){
		Edge &e = edge[i];
		if(!dfn[e.v]){
			tarjan(e.v);
			low[u] = min(low[u],low[e.v]);
		}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
	}
	if(low[u] == dfn[u]){
		num++;	int cur;
		do{
			cur = sta[--stalen];
			vis[cur] = 0;
			tag[cur] = num;
		}while(cur!=u);
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);	cout.tie(0);
	init();
	int m;	cin >> n >> m;
	for(int i = 0; i < m; ++i){
		int ii,a,j,b;	cin >> ii >> a >> j >> b;
		checkadd(ii,a,j,b);
	}
	for(int i = 1; i <= 2*n; ++i){
		if(!dfn[i]) tarjan(i);
	}
	for(int i = 1; i <= n; ++i){
		if(tag[i]==tag[i+n]){
			cout << "IMPOSSIBLE\n";
			return 0;
		}
	}
	cout << "POSSIBLE\n";
	for(int i = 1; i <= n; ++i){
		if(i>1)cout << ' ';
		cout << (tag[i] < tag[i+n]);
	}
	cout << "\n";

	return 0;
}

字典序最小的2-sat問題待更新


2-sat問題主要是如何建模,然後求解!

P4171 [JSOI2010]滿漢全席
分析:
把材料ii如果做成漢式記爲i,如果做爲滿式則記爲i+ni+n,一種材料只能用一種方法做,
所以加邊即可。(具體看代碼)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e2+5;
const int Edgemaxn = 1e3+5;
int n,m;
struct Edge{
    int u,v,nxt;
    Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[Edgemaxn<<2];
int head[maxn],tot;
int dfn[maxn],low[maxn],sta[maxn],stalen,dfnnum,num;
bool vis[maxn]; int tag[maxn];
inline void init(){
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(tag,0,sizeof(tag));
    memset(vis,0,sizeof(vis));
    stalen = 0,dfnnum = 0,num = 0;

    tot = 0;
}
inline void addedge(int u,int v){
    edge[++tot] = Edge(u,v,head[u]);
    head[u] = tot;
}
inline void checkadd(int i,int vali,int j,int valj){
    addedge(i + n*(vali&1),j + n*(valj^1));
    addedge(j + n*(valj&1),i + n*(vali^1));
}
void tarjan(int u){
    low[u] = dfn[u] = ++dfnnum;
    sta[stalen++] = u,vis[u] = 1;
    for(int i = head[u]; ~i ; i = edge[i].nxt){
        Edge&e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v])low[u] = min(low[u],dfn[e.v]);
    }
    if(low[u]==dfn[u]){
        num++; int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = 0;
            tag[cur] = num;
        }while(cur!=u);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int T;
    cin >> T;
    while(T--){
        init();
        cin >> n >> m;;
        for(int i = 0; i < m; ++i){
            char a,b;   int c,d;
            cin >> a >> c >> b >> d;
            checkadd(c,a=='m',d,b=='m');
        }
        for(int i = 1; i <= 2*n; ++i){
            if(!dfn[i]) tarjan(i);
        }
        bool isok = true;
        for(int i = 1; i <= n; ++i){
            if(tag[i]==tag[i+n]){
                isok = false;
                break;
            }
        }
        if(!isok){
            cout <<"BAD" << endl;
            continue;
        }
        cout << "GOOD" << endl;
    }
    return 0;
}

Now or later UVA - 1146

分析: 相鄰兩個着陸時間間隔最小值要最大,顯然要用到二分,對於當前的mid,我們去建圖,如果相鄰兩個之間着陸時間 pmidp \geq mid,就沒關係,如果pmidp \leq mid,則需要去連邊,具體看代碼,然後套板子就好了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e3+5;
struct plane{
    int L,R;
    plane(int l = 0,int r = 0):L(l),R(r){}
}p[maxn];
int n;
struct Edge{
    int u,v,nxt;
    Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[maxn*maxn];
int head[maxn<<1],tot;
int dfn[maxn],low[maxn],sta[maxn],tag[maxn],dfnnum,stalen;
bool vis[maxn]; int num;
inline void init(){
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(tag,0,sizeof(tag));
    num = stalen = dfnnum = tot = 0;
}
inline void addedge(int u,int v){
    edge[++tot] = Edge(u,v,head[u]);
    head[u] = tot;
}
inline void checkadd(int u,int valu,int v,int valv){
    addedge(u + n*(valu&1),v + n*(valv^1));
    addedge(v + n*(valv&1),u + n*(valu^1));
}
inline void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u,vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge&e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v])  low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num;  int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = false;
            tag[cur] = num;
        }while(cur!=u);
    }
}
inline bool check(int mid){
    init();
    for(int i = 1; i <= n; ++i){
        for(int j = i+1; j <= n; ++j){
            if(abs(p[i].L - p[j].L) < mid) checkadd(i,1,j,1);
            if(abs(p[i].L - p[j].R) < mid) checkadd(i,1,j,0);
            if(abs(p[i].R - p[j].L) < mid) checkadd(i,0,j,1);
            if(abs(p[i].R - p[j].R) < mid) checkadd(i,0,j,0);
        }
    }
    for(int i = 1; i <= 2*n; ++i){
        if(!dfn[i]) tarjan(i);
    }
    for(int i = 1; i <= n; ++i){
        if(tag[i]==tag[i+n])
            return false;
    }
    return true;
}
int main()
{
    while(cin >> n){
        int l = 0,r = 0;
        for(int i = 1; i <= n; ++i){
            cin >> p[i].L >> p[i].R;
            r = max(r,p[i].R);
        }
        int ans = 0;
        while(l < r){
            int mid = l + ((r-l+1)>>1);
            if(check(mid)){
                l = mid;
            }else r = mid - 1;
        }
        cout << l << endl;
    }
    return 0;
}

UVALive-3713

這道題至今還沒有AC(個人覺得代碼寫的沒有什麼問題,網上的題解多數已經掛掉了(都是Wrong Answer),筆者先把代碼丟上來,以後再回來看這道題把)

#include <bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 2e5+5;
int age[maxn]; int type[maxn];
struct Edge{
    int u,v,nxt;
    Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[maxn<<2];
int head[maxn],tot;
int dfn[maxn],sta[maxn],low[maxn],dfnnum,num,stalen;
bool vis[maxn]; int tag[maxn];
inline void init(){
    for(int i = 0; i <= 2*n; ++i){
        head[i] = -1;
        dfn[i] = low[i] = vis[i] = tag[i] = 0;
    }
    dfnnum = tot = num = stalen = 0;
}
inline void addedge(int u,int v){
    edge[++tot] = Edge(u,v,head[u]);
    head[u] = tot;
}
inline void checkadd(int u,int valu,int v,int valv){
    addedge(u + n*(valu&1),v + n*(valv^1));
    addedge(v + n*(valv&1),u + n*(valu^1));
}
inline void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u; vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge& e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num;  int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = false;
            tag[cur] = num;
        }while(cur!=u);
    }
}
inline void SAT_2(){
    for(int i = 1; i <= 2*n; ++i){
        if(!dfn[i]) tarjan(i);
    }
    bool isok = true;
    for(int i = 1; i <= n; ++i){
        if(tag[i]==tag[i+n]){
            isok = false;
            break;
        }
    }
    if(!isok){
        printf("No solution.\n");
        return;
    }
    for(int i = 1; i <= n; ++i){
        if(tag[i] < tag[i+n]){
            printf((type[i]==1? "A\n" : "B\n"));
        }
        else printf("C\n");
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m) && n){
        init();
        int sum = 0;
        for(int i = 1; i <= n; ++i){
            scanf("%d",age+i);  sum += age[i];
        }
        for(int i = 1; i <= n; ++i){
            if(age[i]*n >= sum) type[i] = 1;
            else type[i] = 0;
        }
        for(int i = 0,x,y; i < m; ++i){
            scanf("%d%d",&x,&y);
            if(type[x]==type[y]){
                // 兩者類型一樣
                checkadd(x,0,y,0);
            }else{
                checkadd(x,1,y,1);
            }
        }

        SAT_2();
    }
    return 0;
}

Codeforces 1215F Radio Stations

建邊1,2是裸的2-sat問題,套板子就行 對於第一種就是 xiyix_i \vee y_i,所以 (x,0,y,1)(y,0,x,1)
對於第二種就是兩個不能同時選 (y,1,x,0)(x,1,y,0)
那麼對於第三個該怎麼解決?
對於區間 [L,R], 那麼 fLf \geq L 滿足(爲1), fR+1f \geq R+1不滿足(爲0) 且 若[fL+1]1[fL][f\geq L+1]取1,則[f \geq L]必須取1。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章