UPDATE
2-sat问题关键还是在于对于将关系转换到边过程中的思考,对于题目给出的关系,转化是否合法,比如第一题中,两个取值至少满足一个,那么当第一个值不满足的时候,第二个值必须满足,反之,当第一个值满足的时候,第二个值满不满足均可。
关于2-sat问题的建图:
一般是给定m对关系,我们通过这m对关系去建图,
有如下几种例子:
-
(
a
,
b
)
(a,b)
(a,b)不能同时取
那么则是选了a则不能选b,选了b则不能选a,对应到图上为 a → ¬ b a \rightarrow \lnot b a→¬b 和 b → ¬ a b \rightarrow \lnot a b→¬a -
(
a
,
b
)
(a,b)
(a,b)不能同时不取,对应或关系
那么就是 ¬ a → b \lnot a \rightarrow b ¬a→b , ¬ b → a \lnot b \rightarrow a ¬b→a -
(
a
,
b
)
(a,b)
(a,b)得同时取
那么就是 a → b a \rightarrow b a→b , b → a b \rightarrow a b→a,如果要求要么同时取要么同时不取的话加上非的状态即可
洛谷 P4782 【模板】2-SAT
题目描述
有
n
n
n 个布尔变量
x
1
x_1
x1
∼
\sim
∼
x
n
x_n
xn,另有
m
m
m 个需要满足的条件,每个条件的形式都是 「
x
i
x_i
xi 为 true
/ false
或
x
j
x_j
xj 为 true
/ false
」。比如 「
x
1
x_1
x1 为真或
x
3
x_3
x3 为假」、「
x
7
x_7
x7 为假或
x
2
x_2
x2 为假」。
2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入格式
第一行两个整数 n n n 和 m m m,意义如题面所述。
接下来 m m m 行每行 4 4 4 个整数 i i i, a a a, j j j, b b b,表示 「 x i x_i xi 为 a a a 或 x j x_j xj 为 b b b」( a , b ∈ { 0 , 1 } a, b\in \{0,1\} a,b∈{0,1})
输出格式
如无解,输出 IMPOSSIBLE
;否则输出 POSSIBLE
。
下一行 n n n 个整数 x 1 ∼ x n x_1\sim x_n x1∼xn( x i ∈ { 0 , 1 } x_i\in\{0,1\} xi∈{0,1}),表示构造出的解。
思路
从题目中,我们可以知道,对于两个变量,其都有两个值,分别为 1 和 0 ,且两种情况满足其一,举个例子,若
x
x
x 为 1,
y
y
y 为 1,需要满足其一的话,那么我们对应
x
x
x 和
y
y
y 的取值可以为 :
x
=
1
x=1
x=1,
y
=
0
y=0
y=0 或者
x
=
0
x=0
x=0,
y
=
1
y=1
y=1,这只是一个关系,那么对于多个关系表达式,我们依旧可以如此。
接下来继续考虑后续解法,对于这么多条关系,什么时候是成立的,若我们可以从
x
x
x 开始,推出
y
,
z
,
a
,
b
,
c
,
y ,z,a,b,c,
y,z,a,b,c,等一系列的值,并且最后若推出的任何值不与自身相矛盾,即若我
x
=
1
x=1
x=1 时,若推出
x
=
0
x=0
x=0,那么这种时候就是自身矛盾的。
考虑把上述过程转化到算法中,那么对于所有变量,若其为 1 时,我们将其编号为
1
−
n
1-n
1−n,否则编号至
n
+
1
−
2
∗
n
n+1-2*n
n+1−2∗n ,对于上述的关系,若
x
=
1
x=1
x=1 ,
y
=
0
y=0
y=0,则代表
x
x
x 在
1
−
n
1-n
1−n 的范围内的编号向
y
y
y 在
n
+
1
−
2
∗
n
n+1-2*n
n+1−2∗n 的编号连了一条边,最后我们判断是否合法时,则求出每个点的强连通分量,因为对强连通分量而言,其内部每个点都可以相互到达,那么上述所说的,若
x
=
0
x=0
x=0 和
x
=
1
x=1
x=1 处于一个相同的强连通分量,这两个点可以相互到达,则肯定不合法。
最后考虑输出合法解的情况,考虑如下情况:
先给出结论,若一个点,能从其false点到达true点,那么这个点的取值则为1,否则为0。
证明:如图,对于
x
x
x,其false点能到达true点,那么若
x
x
x 为0,我们可以推出
y
y
y 为 1,但又因为
y
y
y 能推出
x
x
x 为 1,则与上述相矛盾,所以
x
x
x 只能为 1。
那么对于false点能到达true点在scc上的意义为:因为求出scc后,其自带一个拓扑序,那么即代表着,false点的拓扑序小于true的拓扑序,又因为scc求出的逆拓扑序,所以即为false点的scc编号大于true点。
最后考虑回归到建边,将刚刚所说的东西抽象化,那么即代表着,对于
x
x
x 和
y
y
y ,我只需要满足其一,所以当
x
x
x 不取的话,那么
y
y
y 必须取,当
y
y
y 不取的话,
x
x
x 就必须取 。那么无非就是
¬
x
→
y
\lnot x \rightarrow y
¬x→y 和
¬
y
→
x
\lnot y \rightarrow x
¬y→x ,对于题目给出其本身值,我不关心,将其看作一个系数即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> p3;
int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
vector<int> e[N];
void add(int u,int v)
{
e[u].push_back(v);
}
int n, m, tot, dfsn[N], ins[N], low[N];
stack<int> s;
vector<vector<int>> scc;
vector<int> b(N);
void dfs(int x)//tarjan求scc
{
low[x] = dfsn[x] = ++tot, ins[x] = 1, s.push(x);
for (auto u : e[x])
{
if (!dfsn[u])
{
dfs(u);
low[x] = min(low[x], low[u]);
}
else if (ins[u])
low[x] = min(low[x], dfsn[u]);
}
if (dfsn[x] == low[x])
{
vector<int> c;
while (1)
{
auto t = s.top();
c.push_back(t);
ins[t] = 0;
s.pop();
b[t] = scc.size();
if (t == x)
break;
}
scc.push_back(c);
}
}
void solve()
{
cin>>n>>m;
auto get=[&](int x){//若x为赋值,我们就把他变到n-2*n范围
return x<0? n-x : x;
};
for(int i=1;i<=m;i++){
int a,x,b,y;
cin>>a>>x>>b>>y;
x= x==0 ? -1 : 1;
y= y==0 ? -1 : 1;
add(get(-1*a*x),get(b*y));
add(get(-1*b*y),get(a*x));
}
for(int i=1;i<=n*2;i++){
if(!dfsn[i]) dfs(i);
}
int ok=1;
for(int i=1;i<=n;i++){
if(b[i]==b[i+n]) ok=0;
}
if(ok){
cout<<"POSSIBLE"<<endl;
for(int i=1;i<=n;i++){
if(b[i]<b[i+n]) cout<<1<<" ";
else cout<<0<<" ";
}
cout<<endl;
}
else cout<<"IMPOSSIBLE"<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
// cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
Codeforces Round 944 (Div. 4) H. ±1
思路
我们考虑对每一列排序完成后的情况,那么若构造合法,对于 x , y , z x,y,z x,y,z 三个数而言,若 x x x 为 -1,那么对应的 y , z y,z y,z 必须为 1,因此就转化为了2-sat问题,因为现在就相当于给定 n n n 组关系,判断是否有一组合法解能让这 n n n 组关系成立即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> ar;
const int mod = 1e9+7;
const int maxv = 4e6 + 5;
// #define endl "\n"
int n, m, tot, dfsn[N], ins[N], low[N];
stack<int> s;
vector<int> e[N];
vector<vector<int>> scc;
vector<int> b(N);
void dfs(int x)
{
low[x] = dfsn[x] = ++tot, ins[x] = 1, s.push(x);
for (auto u : e[x])
{
if (!dfsn[u])
{
dfs(u);
low[x] = min(low[x], low[u]);
}
else if (ins[u])
low[x] = min(low[x], dfsn[u]);
}
if (dfsn[x] == low[x])
{
vector<int> c;
while (1)
{
auto t = s.top();
c.push_back(t);
ins[t] = 0;
s.pop();
b[t] = scc.size();
if (t == x)
break;
}
scc.push_back(c);
}
}
void solve()
{
cin>>n;
vector<vector<int> > a(5,vector<int>(n+5));
for(int i=1;i<=3;i++){
for(int j=1;j<=n;j++) cin>>a[i][j];
}
tot=0;
scc.clear();
for(int i=1;i<=2*n;i++){
dfsn[i]=low[i]=ins[i]=b[i]=0;
e[i].clear();
}
auto get=[&](int x)
{
if(x<0) return n-x;
else return x;
};
for(int i=1;i<=n;i++){
int x=a[1][i],y=a[2][i],z=a[3][i];//分别考虑x,y,z为-1的情况
e[get(-x)].push_back(get(y));
e[get(-x)].push_back(get(z));
e[get(-y)].push_back(get(x));
e[get(-y)].push_back(get(z));
e[get(-z)].push_back(get(x));
e[get(-z)].push_back(get(y));
}
for(int i=1;i<=2*n;i++){
if(!dfsn[i]) dfs(i);
}
int ok=1;
for(int i=1;i<=n;i++){
if(b[i]==b[i+n]) ok=0;
}
if(ok) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
2018-2019 ACM-ICPC Asia Seoul Regional K TV Show Game
题意:
有 k ( k > 3 ) k(k>3) k(k>3) 盏灯,每盏灯是红色或者蓝色,但是初始的时候不知道灯的颜色。有 n n n 个人,每个人选择 3 盏灯并猜灯的颜色。一个人猜对两盏灯或以上的颜色就可以获得奖品。判断是否存在一个灯的着色方案使得每个人都能领奖,若有则输出一种灯的着色方案。
思路:
和上一题差不多,我们考虑每个人猜灯的情况,即猜错一次,猜对两次,那么这样就转化为了2-sat问题。
我们把灯的颜色转化为 1 和 -1,然后每次猜错对应两次猜对,即对于
x
,
y
,
z
x,y,z
x,y,z ,
¬
x
→
y
\lnot x \rightarrow y
¬x→y 和
¬
x
→
z
\lnot x \rightarrow z
¬x→z,然后使用tarjan去判断即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> p3;
int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
vector<int> e[N];
void add(int u,int v)
{
e[u].push_back(v);
}
int n, m, tot, dfsn[N], ins[N], low[N];
stack<int> s;
vector<vector<int>> scc;
vector<int> b(N);
void dfs(int x)
{
low[x] = dfsn[x] = ++tot, ins[x] = 1, s.push(x);
for (auto u : e[x])
{
if (!dfsn[u])
{
dfs(u);
low[x] = min(low[x], low[u]);
}
else if (ins[u])
low[x] = min(low[x], dfsn[u]);
}
if (dfsn[x] == low[x])
{
vector<int> c;
while (1)
{
auto t = s.top();
c.push_back(t);
ins[t] = 0;
s.pop();
b[t] = scc.size();
if (t == x)
break;
}
scc.push_back(c);
}
}
void solve()
{
cin>>n>>m;
auto get=[&](int x){
return x<0? n-x : x;
};
for(int i=1;i<=m;i++){
int a,b,c;
char la,lb,lc;
cin>>a>>la>>b>>lb>>c>>lc;
int xa,xb,xc;
if(la=='R') xa=1;
else xa=-1;
if(lb=='R') xb=1;
else xb=-1;
if(lc=='R') xc=1;
else xc=-1;
add(get(-a*xa),get(b*xb));
add(get(-a*xa),get(c*xc));
add(get(-b*xb),get(a*xa));
add(get(-b*xb),get(c*xc));
add(get(-c*xc),get(a*xa));
add(get(-c*xc),get(b*xb));
}
int ok=1;
for(int i=1;i<=n*2;i++) if(!dfsn[i]) dfs(i);
for(int i=1;i<=n;i++){
if(b[i]==b[i+n]) ok=0;
}
if(ok){
for(int i=1;i<=n;i++){
if(b[i]<b[i+n]) cout<<'R';
else cout<<'B';
}
cout<<endl;
}
else cout<<-1<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
// cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}