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