首页 > 代码库 > 浅谈2-sat的问题的构造与求解

浅谈2-sat的问题的构造与求解

2-sat问题是一种常见的问题。给定若干个01变量,变量之间满足一些二元约束,求是否有解存在。若存在,给出可行解或按照字典序给出最优解。

下面给出与其对应的图论模型:给每个变量i设立2个点,我的习惯是记为T(i),F(i),分别表示其值取1,0.

下面考虑的便是如何进行限制了。


一般的限制形式均如下所示:

变量i取x时,变量j只能取y,那么表示i取x的点向表示j取y的点连一条有向边。表示推出关系。

类似的,若表示变量i取x时,变量j不能取y,那么表示i取x的点向表示j取~y的点连一条有向边。因为每个变量必定有值,且只有一个值。

以上这些限制每次都需要连两条边,即原命题和其逆否命题。

有一些另外的限制比较特殊:即变量i只能取x,那么表示i取~x的点向表示i取x的点连一条有向边.若表示变量i不能取x,那么表示i取x的点向表示i取~x的点连一条有向边.这些限制只连一条边。


接下来,利用Tarjan算法求解强联通分量,若表示某个变量为0的点和表示这个变量为1的点在同一个强联通分量中,表示存在矛盾,2-sat问题无解。


下面看一下如何求出一组可行解。

我们构造求出的DAG的反图,依照拓扑序依次进行处理。对于当前点,若没有被染色,则染色为1,并将这个连通分量中所有点的另一个解所对应的点所在的分量及其子孙均染色为2.(注意,这是在反图中。)染色就递归去染就好了,当遇到一个已经被染色为2的点就不向下染色了。


那么最终每个变量的解就是被染色为1的分量所包含的该变量所对应的解。


下面来看几道题:

POJ3207

Sol:以这条边在圈内作为0,在圈外作为1,限制就是如果两条边区间相交,那么值不能相同。那么T(i)->F(j),T(j)->F(i).只需判断这个2-sat问题是否有解即可。

Code:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;

#define N 510
int l[N], r[N];

int head[N << 1], next[N * N << 2], end[N * N << 2];
void addedge(int a, int b) {
	static int q = 1;
	end[q] = b;
	next[q] = head[a];
	head[a] = q++;
}

#define T(i) (i << 1)
#define F(i) ((i << 1) | 1)

int dfn[N << 1], low[N << 1], tclock, stack[N << 1], top, belong[N << 1], num;
bool instack[N << 1];

void dfs(int x) {
	dfn[x] = low[x] = ++tclock;
	stack[++top] = x;
	instack[x] = 1;
	for(int j = head[x]; j; j = next[j]) {
		if (!dfn[end[j]])
			dfs(end[j]), low[x] = min(low[x], low[end[j]]);
		else if (instack[end[j]])
			low[x] = min(low[x], dfn[end[j]]);
	}
	if (low[x] == dfn[x]) {
		++num;
		while(1) {
			int i = stack[top--];
			belong[i] = num;
			instack[i] = 0;
			if (i == x)
				break;
		}
	}
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	
	register int i, j;
	int a, b;
	for(i = 1; i <= m; ++i) {
		scanf("%d%d", &a, &b);
		l[i] = ++a;
		r[i] = ++b;
		if (l[i] > r[i])
			swap(l[i], r[i]);
	}
	
	for(i = 1; i <= m; ++i) {
		for(j = i + 1; j <= m; ++j) {
			if (l[j] > l[i] && l[j] < r[i] && r[j] > r[i]) {
				addedge(T(i), F(j));
				addedge(F(i), T(j));
				addedge(T(j), F(i));
				addedge(F(j), T(i));
			}
		}
	}
	
	for(i = T(1); i <= F(m); ++i)
		if (!dfn[i])
			dfs(i);
			
	bool find = 0;
	for(i = 1; i <= m; ++i)
		if (belong[T(i)] == belong[F(i)]) {
			find = 1;
			break;
		}
	
	if (find)
		puts("the evil panda is lying again");
	else
		puts("panda is telling the truth...");
		
	return 0;
}

BZOJ1997

Sol:基本和上道题类似。也只需要判断是否有解。

Code:

#include <cctype>
#include <cstdio>
#include <cstring>
#include <climits>
#include <algorithm>
using namespace std;
 
inline int getc() {
    static const int L = 1 << 15;
    static char buf[L], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, L, stdin);
        if (S == T)
            return EOF;
    }
    return *S++;
}
inline int getint() {
    int c;
    while(!isdigit(c = getc()));
    int tmp = c - '0';
    while(isdigit(c = getc()))
        tmp = (tmp << 1) + (tmp << 3) + c - '0';
    return tmp;
}
 
#define N 210
#define M 10010
int hash[N];
 
struct Edge {
    int f, t;
    Edge(int _f = 0, int _t = 0):f(_f),t(_t){}
}E[M], sav[M];
int top;
 
struct Graph {
    int head[1210], next[819200], end[819200], ind;
    int dfn[1210], low[1210], tclock, stack[1210], top, belong[1210], cnt;
    bool instack[1210];
    void reset() {
        ind = tclock = top = cnt = 0;
        memset(head, -1, sizeof(head));
        memset(dfn, 0, sizeof(dfn));
    }
    void addedge(int a, int b) {
        int q = ind++;
        end[q] = b;
        next[q] = head[a];
        head[a] = q;
    }
    void dfs(int x) {
        dfn[x] = low[x] = ++tclock;
        instack[x] = 1;
        stack[++top] = x;
        for(int j = head[x]; j != -1; j = next[j]) {
            if (!dfn[end[j]]) {
                dfs(end[j]);
                low[x] = min(low[x], low[end[j]]);
            }
            else if (instack[end[j]])
                low[x] = min(low[x], dfn[end[j]]);
        }
        if (low[x] == dfn[x]) {
            ++cnt;
            while(1) {
                int i = stack[top--];
                belong[i] = cnt;
                instack[i] = 0;
                if (x == i)
                    break;
            }
        }
    }
}G;
 
#define T(i) (i * 2 - 2)
#define F(i) (i * 2 - 1)
 
inline bool is_intersect(int i, int j) {
    return sav[j].f > sav[i].f && sav[j].f < sav[i].t && sav[j].t > sav[i].t;
}
 
int main() {
    int T = getint();
     
    register int i, j;
    int n, m;
    while(T--) {
        n = getint(), m = getint();
        for(i = 1; i <= m; ++i)
            E[i].f = getint(), E[i].t = getint();
        int x;
        for(i = 1; i <= n; ++i) {
            x = getint();
            hash[x] = i;
        }
         
        if (m > 3 * n + 6) {
            puts("NO");
            continue;
        }
         
        top = 0;
        int f, t;
        for(i = 1; i <= m; ++i) {
            f = hash[E[i].f], t = hash[E[i].t];
            if (f > t)
                swap(f, t);
            if (!((f + 1 == t) || (f == 1 && t == n))) {
                sav[++top] = Edge(f, t);
            }
        }
         
        G.reset();
        for(i = 1; i <= top; ++i) {
            for(j = i + 1; j <= top; ++j) {
                if (is_intersect(i, j) || is_intersect(j, i)) {
                    G.addedge(T(i), F(j));
                    G.addedge(F(i), T(j));
                    G.addedge(T(j), F(i));
                    G.addedge(F(j), T(i));
                }
            }
        }
         
        for(i = T(1); i <= F(top); ++i)
            if (!G.dfn[i])
                G.dfs(i);
         
        bool find = 0;
        for(i = 1; i <= top; ++i)
            if (G.belong[T(i)] == G.belong[F(i)]) {
                find = 1;
                break;
            }
         
        if (find)
            puts("NO");
        else
            puts("YES");
    }
     
    return 0;
}

POJ3678

Sol:一堆乱七八糟的位运算。。。涉及到某变量强制取值的建模技巧。只需判断是否有解。

Code:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;

#define N 1010
#define M 1000010
int head[N << 1], end[M << 2], next[M << 2];
void addedge(int a, int b) {
	static int q = 1;
	end[q] = b;
	next[q] = head[a];
	head[a] = q++;
}

#define T(i) (i << 1)
#define F(i) ((i << 1) | 1)

int dfn[N << 1], low[N << 1], tclock, stack[N << 1], top, belong[N << 1], num;
bool instack[N << 1];
void dfs(int x) {
	dfn[x] = low[x] = ++tclock;
	stack[++top] = x;
	instack[x] = 1;
	for(int j = head[x]; j; j = next[j]) {
		if (!dfn[end[j]]) {
			dfs(end[j]);
			low[x] = min(low[x], low[end[j]]);
		}
		else if (instack[end[j]])
			low[x] = min(low[x], dfn[end[j]]);
	}
	if (dfn[x] == low[x]) {
		++num;
		while(1) {
			int i = stack[top--];
			belong[i] = num;
			instack[i] = 0;
			if (i == x)
				break;
		}
	}
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	
	register int i;
	char s[10];
	int a, b, x;
	
	for(i = 1; i <= m; ++i) {
		scanf("%d%d%d%s", &a, &b, &x, s);
		++a, ++b;
		if (s[0] == 'A') {
			if (x) {
				addedge(F(a), T(a));
				addedge(F(b), T(b));
			}
			else {
				addedge(T(a), F(b));
				addedge(T(b), F(a));
			}
		}
		if (s[0] == 'O') {
			if (x) {
				addedge(F(a), T(b));
				addedge(F(b), T(a));
			}
			else {
				addedge(T(a), F(a));
				addedge(T(b), F(b));
			}
		}
		if (s[0] == 'X') {
			if (x) {
				addedge(T(a), F(b));
				addedge(F(a), T(b));
				addedge(T(b), F(a));
				addedge(F(b), T(a));
			}
			else {
				addedge(T(a), T(b));
				addedge(F(a), F(b));
				addedge(T(b), T(a));
				addedge(F(b), F(a));
			}
		}
	}
	
	for(i = T(1); i <= F(n); ++i)
		if (!dfn[i])
			dfs(i);
	
	bool find = 0;
	for(i = 1; i <= n; ++i) {
		if (belong[T(i)] == belong[F(i)]) {
			find = 1;
			break;
		}
	}
	
	if (find)
		puts("NO");
	else
		puts("YES");
		
	return 0;
}

POJ3683

Sol:每个婚礼分为是开头还是结尾作为01取值,建模很简单,若区间相交则矛盾即可。

比较锻炼代码能力的一道题。。。

Code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;

#define N 1010
int l[N], r[N], len[N];

int get(char *s) {
	return 600*(s[0]-'0')+60*(s[1]-'0')+10*(s[3]-'0')+(s[4]-'0');
}

int head[N << 1], next[N * N << 3], end[N * N << 3];
void addedge(int a, int b) {
	static int q = 1;
	end[q] = b;
	next[q] = head[a];
	head[a] = q++;
}

#define T(i) (i << 1)
#define F(i) ((i << 1) | 1)

int dfn[N << 1], low[N << 1], tclock, stack[N << 1], top, belong[N << 1], num;
vector<int> sav[N << 1];
bool instack[N << 1];
void dfs(int x) {
	dfn[x] = low[x] = ++tclock;
	stack[++top] = x;
	instack[x] = 1;
	for(int j = head[x]; j; j = next[j]) {
		if (!dfn[end[j]]) {
			dfs(end[j]);
			low[x] = min(low[x], low[end[j]]);
		}
		else if (instack[end[j]])
			low[x] = min(low[x], dfn[end[j]]);
	}
	if (dfn[x] == low[x]) {
		++num;
		while(1) {
			int i = stack[top--];
			instack[i] = 0;
			belong[i] = num;
			sav[num].push_back(i);
			if (x == i)
				break;
		}
	}
}

queue<int> q;
struct Graph {
	int head[N << 1], next[N * N << 3], end[N * N << 3], in[N << 1], ind, col[N << 1];
	void reset() {
		ind = 0;
		memset(head, -1, sizeof(head));
	}
	void addedge(int a, int b) {
		int q = ind++;
		end[q] = b;
		next[q] = head[a];
		head[a] = q;
		++in[b];
	}
	void Paint(int x) {
		col[x] = 2;
		for(int j = head[x]; j != -1; j = next[j])
			if (col[end[j]] != 2)
				Paint(end[j]);
	}
	void TopoSort() {
		int i, j;
		for(i = 1; i <= num; ++i)
			if (!in[i])
				q.push(i);
		while(!q.empty()) {
			i = q.front();
			q.pop();
			if (!col[i]) {
				col[i] = 1;
				int size = sav[i].size();
				for(j = 0; j < size; ++j)
					Paint(belong[sav[i][j] ^ 1]);
			}
			for(j = head[i]; j != -1; j = next[j])
				if (!--in[end[j]])
					q.push(end[j]);
		}
	}			
}G;

bool isnot_insect(int l1, int r1, int l2, int r2) {
	return r1 <= l2 || r2 <= l1;
}

void print(int x) {
	int t = x % 60;
	printf("%02d:%02d", (x - t) / 60, t);
}

int main() {
	int n;
	scanf("%d", &n);
	
	char s1[10], s2[10];
	int i, j;
	
	for(i = 1; i <= n; ++i) {
		scanf("%s%s%d", s1, s2, &len[i]);
		l[i] = get(s1);
		r[i] = get(s2);
	}
	
	for(i = 1; i <= n; ++i) {
		for(j = i + 1; j <= n; ++j) {
			if (!isnot_insect(l[i], l[i] + len[i], l[j], l[j] + len[j])) {
				addedge(T(i), F(j));
				addedge(T(j), F(i));
			}
			if (!isnot_insect(l[i], l[i] + len[i], r[j] - len[j], r[j])) {
				addedge(T(i), T(j));
				addedge(F(j), F(i));
			}
			if (!isnot_insect(r[i] - len[i], r[i], l[j], l[j] + len[j])) {
				addedge(F(i), F(j));
				addedge(T(j), T(i));
			}
			if (!isnot_insect(r[i] - len[i], r[i], r[j] - len[j], r[j])) {
				addedge(F(i), T(j));
				addedge(F(j), T(i));
			}
		}
	}
	
	for(i = T(1); i <= F(n); ++i)
		if (!dfn[i])
			dfs(i);
	
	bool find = 0;
	for(i = 1; i <= n; ++i)
		if (belong[T(i)] == belong[F(i)]) {
			find = 1;
			break;
		}
	
	if (find) {
		puts("NO");
		return 0;
	}
	
	G.reset();
	for(i = T(1); i <= F(n); ++i)
		for(j = head[i]; j; j = next[j])
			if (belong[i] != belong[end[j]])
				G.addedge(belong[end[j]], belong[i]);
	G.TopoSort();
	puts("YES");
	for(i = 1; i <= n; ++i) {
		if (G.col[belong[T(i)]] == 1)
			print(l[i]), putchar(' '), print(l[i] + len[i]);
		else
			print(r[i] - len[i]), putchar(' '), print(r[i]);
		puts("");
	}
	
	return 0;
}

BZOJ1823

Sol:十分的裸题。。。

Code:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;
 
#define N 110
#define M 1010
struct Graph {
    int head[N << 1], next[M << 1], end[M << 1], ind;
    int dfn[N << 1], low[N << 1], tclock, stack[N << 1], top, belong[N << 1], num;
    bool instack[N << 1];
    void reset() {
        ind = tclock = top = num = 0;
        memset(dfn, 0, sizeof(dfn));
        memset(head, -1, sizeof(head));
    }
    void addedge(int a, int b) {
        int q = ind++;
        end[q] = b;
        next[q] = head[a];
        head[a] = q;
    }
    void dfs(int x) {
        dfn[x] = low[x] = ++tclock;
        instack[x] = 1;
        stack[++top] = x;
        for(int j = head[x]; j != -1; j = next[j]) {
            if (!dfn[end[j]]) {
                dfs(end[j]);
                low[x] = min(low[x], low[end[j]]);
            }
            else if (instack[end[j]])
                low[x] = min(low[x], dfn[end[j]]);
        }
        if (dfn[x] == low[x]) {
            ++num;
            while(1) {
                int i = stack[top--];
                belong[i] = num;
                instack[i] = 0;
                if (x == i)
                    break;
            }
        }
    }
}G;
 
#define T(i) (i << 1)
#define F(i) ((i << 1) | 1)
 
int getint(char *s) {
    int len = strlen(s);
    int res = 0;
    for(int i = 1; i < len; ++i)
        res = (res << 3) + (res << 1) + s[i] - '0';
    return res;
}
 
int main() {
    int Case;
    scanf("%d", &Case);
     
    int n, m;
    char s1[60], s2[60];
    register int i, j;
    bool b1, b2;
    int n1, n2;
     
    while(Case--) {
        G.reset();
         
        scanf("%d%d", &n, &m);
        for(i = 1; i <= m; ++i) {
            scanf("%s%s", s1, s2);
            b1 = (s1[0] == 'm');
            b2 = (s2[0] == 'm');
            n1 = getint(s1);
            n2 = getint(s2);
            if (b1 && b2)
                G.addedge(F(n1), T(n2)), G.addedge(F(n2), T(n1));
            if (b1 && !b2)
                G.addedge(F(n1), F(n2)), G.addedge(T(n2), T(n1));
            if (!b1 && b2)
                G.addedge(T(n1), T(n2)), G.addedge(F(n2), F(n1));
            if (!b1 && !b2)
                G.addedge(T(n1), F(n2)), G.addedge(T(n2), F(n1));
        }
         
        for(i = T(1); i <= F(n); ++i)
            if (!G.dfn[i])
                G.dfs(i);
         
        bool find = 0;
        for(i = 1; i <= n; ++i)
            if (G.belong[T(i)] == G.belong[F(i)]) {
                find = 1;
                break;
            }
        if (find)
            puts("BAD");
        else
            puts("GOOD");
    }
     
    return 0;
}

就到这里结束如何?

浅谈2-sat的问题的构造与求解