2-SAT问题

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 ¬ab , ¬ b → a \lnot b \rightarrow a ¬ba
  • ( a , b ) (a,b) (a,b)得同时取
    那么就是 a → b a \rightarrow b ab , b → a b \rightarrow a ba,如果要求要么同时取要么同时不取的话加上非的状态即可

洛谷 P4782 【模板】2-SAT

题目描述

n n n 个布尔变量 x 1 x_1 x1 ∼ \sim x n x_n xn,另有 m m m 个需要满足的条件,每个条件的形式都是 「 x i x_i xitrue / false x j x_j xjtrue / 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 x1xn 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, yzabc等一系列的值,并且最后若推出的任何值不与自身相矛盾,即若我 x = 1 x=1 x=1 时,若推出 x = 0 x=0 x=0,那么这种时候就是自身矛盾的。
考虑把上述过程转化到算法中,那么对于所有变量,若其为 1 时,我们将其编号为 1 − n 1-n 1n,否则编号至 n + 1 − 2 ∗ n n+1-2*n n+12n ,对于上述的关系,若 x = 1 x=1 x=1 y = 0 y=0 y=0,则代表 x x x 1 − n 1-n 1n 的范围内的编号向 y y y n + 1 − 2 ∗ n n+1-2*n n+12n 的编号连了一条边,最后我们判断是否合法时,则求出每个点的强连通分量,因为对强连通分量而言,其内部每个点都可以相互到达,那么上述所说的,若 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 ¬xy ¬ y → x \lnot y \rightarrow x ¬yx ,对于题目给出其本身值,我不关心,将其看作一个系数即可。

#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 ¬xy ¬ x → z \lnot x \rightarrow z ¬xz,然后使用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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值