专栏文章
题解:P4782 【模板】2-SAT
P4782题解参与者 4已保存评论 7
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 7 条
- 当前快照
- 1 份
- 快照标识符
- @mioojhit
- 此快照首次捕获于
- 2025/12/02 22:35 3 个月前
- 此快照最后确认于
- 2025/12/02 22:35 3 个月前
尝试用了新版格式。我发现氧洛谷在新版格式有不小 bug,在此催更。
定义
2-SAT,简单的说就是给出 个布尔方程,每个方程和两个变量相关,如 ,表示变量 至少满足一个。然后判断是否存在可行方案,显然可能有多种选择方案,一般题中只需要求出一种即可。另外, 表示 取反。
例题引入:洛谷 P4782
有 个布尔变量 ,另有 个需要满足的条件,每个条件的形式都是“ 为
true / false 或 为 true / false”。比如“ 为真或 为假”、“ 为假或 为假”。2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
转化题意为:装饰一个房间,有 种装饰品。有 个人,每人提出两点要求(要不要用某种装饰品),当其中至少有一条被满足时(当然允许两条都满足),这个人会感到满意。问你是否可以让所有人都感到满意,进一步的,还可以让你输出让所有人都感到满意的方案。
重点:建图
使用布尔方程表示上述问题。设 表示要用第 A 种装饰品( 就表示不用第 A 种装饰品)。如果有个人提出的要求分别是 和 ,即 (变量 至少满足一个)。对这些变量关系建有向图,则把 成立或不成立用图中的点表示, ,表示 不成立则 一定成立;同理, 不成立则 一定成立。建图之后,我们就可以使用缩点算法来求解 2-SAT 问题了。
| 原式 | 建图 |
|---|---|
许多 2-SAT 问题都需要找出如 不成立,则 成立的关系。
求解
思考如果两点在同一强连通分量里有什么含义。根据前文边的逻辑意义可知:若两点在同一强连通分量内,则这两点代表的条件要么都满足,要么都不满足。
建图周我们跑一遍 Tarjan 找 SCC,判断对于任意布尔变量 , 表示 成立的点和表示 不成立的点是否在同一个 SCC 中(同一条件不可能既满足又不满足,或既不满足,又并非不满足),若有则输出无解,否则有解。
输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果变量 的拓扑序在 之后,那么取 值为真。应用到 Tarjan 算法的缩点,即 所在 SCC 编号在 之前时,取 为真。因为 Tarjan 算法求强连通分量时使用了栈,如果跑完 Tarjan 缩点之后呈现出的拓扑序更大,在 Tarjan 会更晚被遍历到,就会更早地被弹出栈而缩点,分量编号会更小,所以 Tarjan 求得的 SCC 编号相当于反拓扑序。
算法会把整张图遍历一遍,并且在结算答案时复杂度为 ,认为 和 同阶,复杂度为 。
代码
CPP#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int N=2e6+2;
int n,m,dfn[N],low[N],t,tot,head[N],a[N];
bool vis[N];
stack<int> s;
struct node{int to,Next;}e[N];
void adde(int u,int v){
e[++tot].to=v;
e[tot].Next=head[u];
head[u]=tot;
}
void Tarjan(int u){
dfn[u]=low[u]=++t;
s.push(u);
vis[u]=1;
for(int i=head[u];i;i=e[i].Next){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int cur;
++tot;
do{
cur=s.top();
s.pop();
vis[cur]=0;
a[cur]=tot;
}while(cur!=u);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,I,J,A,B;i<=m;i++){
scanf("%d%d%d%d",&I,&A,&J,&B);
adde(A?I+n:I,B?J:J+n);
adde(B?J+n:J,A?I:I+n);
}
tot=0;
for(int i=1;i<=(n<<1);i++) if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++){
if(a[i]==a[i+n]){
printf("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(int i=1;i<=n;i++){
putchar(a[i]<a[i+n]?'1':'0');
putchar(' ');
}
return 0;
}
相关推荐
评论
共 7 条评论,欢迎与作者交流。
正在加载评论...