前置知识
概念
- 用于处理拥有斜对称性的问题(在下文会提到)
- 运用了并查集思想
2-SAT
斜对称性
这是一张斜对称性概念图
- 如在例题P4782中,斜对称性就是 \(x_0 = y_1\) 且 \(x_1 = y_0\) (这是抽象意思,具体见代码实现)
易错地方
- 代码中是有向边,为什么不是无向边?
- 因为 2-SAT 是 并查集进阶版本,仅仅只是为了合并,所以不可以建无向边,因为不可以重复合并
Code
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <map>
using namespace std;
using LL = long long;
const int MAXN = 2e6 + 3; // 这里 MAXN 需要乘 2
int n, m, sum_k = 0; // sum_k 表示块的数量
int depth, s_len, low[MAXN], dep[MAXN], s[MAXN]; // 缩点用的变量
int root[MAXN], vis[MAXN]; // 缩点后得到的答案
vector<int> eg[MAXN];
int dfs_small(int x, int dad){ // 模板缩点
if(dep[x]){
return (root[x] > 0 ? 1e9 : low[x]);
}
depth++, dep[x] = low[x] = depth, s[++s_len] = x;
for(int nxt : eg[x]) low[x] = min(low[x], dfs_small(nxt, x));
if(low[x] == dep[x]){
sum_k++;
for( ; s_len > 0 && s[s_len] != x; s_len--){
vis[s[s_len]] = sum_k, root[s[s_len]] = x; // 这里不需要合并边,后面没有用到
}
vis[x] = sum_k, root[x] = x, s_len--;
}
return low[x];
}
void as_small(){
depth = 0, s_len = 0;
for(int i = 1; i <= 2 * n; i++) dfs_small(i, 0); // 关系图不一定联通
}
bool as(){ // 判断 IMPOSSIBLE
for(int i = 1; i <= n; i++){
if(vis[i] == vis[i + n]) return 1;
}
return 0;
}
int main(){
cin >> n >> m;
for(int i = 1, X, Y, A, B; i <= m; i++){
cin >> X >> A >> Y >> B;
eg[(A^1)*n+X].push_back(B*n+Y); // 借助斜对称性建边
eg[(B^1)*n+Y].push_back(A*n+X); // 不可以建双向边,因为不可以重复合并
}
as_small();
if(as()){
cout << "IMPOSSIBLE";
return 0;
}
cout << "POSSIBLE\n";
for(int i = 1; i <= n; i++){ // 该题目有多种方案,这是任意合法方案
cout << (vis[root[i]] > vis[root[i + n]]) << " "; // 0在1之前
}
return 0;
}