抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

对于x1...xnx_1 ... x_n,考虑如下的mm条限制:

xix_i为true/false或者xjx_j为true/false

我们举个例子,如果要求的是xix_i为true或者xjx_j为false

那么如果xix_i为false,xjx_j就必须为true

反之,如果xjx_j为true,xix_i就必须为true

这样,我们巧妙地将或问题转换成与问题

插句题外话,其实或运算可以转换成与运算,即x and y=not ((not x) or (not y))x \text{ and } y = \text{not }((\text{not }x) \text{ or } (\text{not y}))

这个性质在红石里面很有用

好了,发现怎么转换以后,我们考虑将问题抽象化,不妨建一个有向图。

事实上,我们建的是两倍节点的图,前面的nn个点代表xi==truex_i==true,后面nn个点代表xi==falsex_i==false

<u,v><u,v>表示uu的值为真的话,vv的值就必须为真。

注意到我们建立的是有向图,所以刚才的边并不代表vv的值为真,能推出uu的值为真。

我们考虑如何判断矛盾,我们在求出这个图的强连通分量,同一强连通分量中的值一定相等,如果xi==truex_i==true代表的节点和xi==falsex_i==false的节点在同一强连通分量,那么不合法。

如果没有这种矛盾,是可以证明一定有一个合法的解的。(然鹅我不会证)

考虑把所有强连通分量缩成一个点,变成一个拓扑图(当然在代码里面不用写)。

注意这里每个点代表的是原图的一个强连通分量。

假设我们现在选了一个拓扑序比较靠前的节点uu,钦定它的值为true​(标成红色)

那么拓扑序大于它的节点都必须为true。

这样标成true的节点太多了,可能造成很多矛盾,不符合基本法。

考虑一个更优的方法:

我们选一个拓扑序靠后的节点vv,钦定它的值为true,这样只要把较少点标成true。

不知道高到哪里去了。

所以,根据贪心的原则我们优先选择拓扑序靠后的节点设成true

再举个例子:在这种情况下设u=false,因为它的拓扑序比较靠后,如果设u=true显然会造成矛盾。

注意到tarjantarjan的拓扑序是从大到小的,所以输出时是这个样子的:

1
2
3
for (register int i=1;i<=n;++i){
printf("%d ",col[i]>col[i+n]);
}

好了,算法讲解好了,我们做一道练习题(做之前先把算法理解透彻):

P4782 【模板】2-SAT 问题

这里我们设xi==truex_i ==true的节点在1   n1 \text{ ~ } nxi==falsex_i==false的节点在n+1   2nn+1 \text{ ~ } 2n

加边的时候这样加,避免讨论:

1
2
AddEdge(u+(!a)*n,v+b*n);
AddEdge(v+(!b)*n,u+a*n);

注意数组开两倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define MAXN 2000005
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+(ch^'0');
ch=getchar();
}
return x*f;
}
vector<int>G[MAXN];
inline void AddEdge(int u,int v){
G[u].push_back(v);
}
int dfn[MAXN],low[MAXN],cnt;
stack<int>stk;
int scc,col[MAXN];
void tarjan(int u){
dfn[u]=low[u]=++cnt;
stk.push(u);
for (register int i=0;i<G[u].size();++i){
int v=G[u][i];
if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if (!col[v]) low[u]=min(low[u],dfn[v]);
}
if (low[u]==dfn[u]){
++scc;
do{
col[u]=scc;
u=stk.top(),stk.pop();
}while (low[u]!=dfn[u]);
}
}
int main(){
int n=read(),m=read();
for (register int i=1;i<=m;++i){
int u=read(),a=read(),v=read(),b=read();
AddEdge(u+(!a)*n,v+b*n);
AddEdge(v+(!b)*n,u+a*n);
}
for (register int i=1;i<=(n<<1);++i){
if (!dfn[i]) tarjan(i);
}
for (register int i=1;i<=n;++i){
if (col[i]==col[i+n]){
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for (register int i=1;i<=n;++i){
printf("%d ",col[i]>col[i+n]);
}
}

评论