专栏文章

题解:P4782 【模板】2-SAT

P4782题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@miovbh4n
此快照首次捕获于
2025/12/03 01:44
3 个月前
此快照最后确认于
2025/12/03 01:44
3 个月前
查看原文

P4782 【模板】2-SAT

怎么求2--SAT?

因为一个节点只有两个状态(真或假),那我们就可以拆点,将一个节点拆成两个点(两排),上面一排全是假,下面一排全是真。
然后根据题意可以知道如果 XjbX_j ≠ b 为真,那么 Xi=aX_i = a,反之同理,由假推真。

证明

X1=1X_1 = 1X3=0X_3 = 0,如果 X30X_3 ≠ 0 那么一定有 X1=1X_1 = 1, 因为他要至少满足一个条件。

建图

那我们就可以开始建边了,将 XiX_i 为真的状态的边连到 XjX_j 为假的状态的编号上,再从 XjX_j 以同样的方式连到 XiX_i 上,这样建边就完成了。
然后 tarjan 就比较板子了,自己 c 之前的代码

判解

我们只需要判断有没有某个节点的为假和为真的编号在强连通分量里相等就行了,举个例子你就懂了:X1=1X_1 = 1X2=1X_2 = 1X1=1X_1 = 1X2=0X_2 = 0 这个就是矛盾的,如果 X11X_1 ≠ 1,那么一定有 X2=1X_2 = 1,但当 X2=1X_2 = 10≠ 0 时,就会触发第二个条件一定有 X1=1X_1 = 1,这样就矛盾了。

细节

输出方案时要输出编号小的,因为拓扑排序是小编号有较大的拓扑序,由于它又依赖于其他节点,为避免后效性矛盾,所以要选择较大的拓扑序也就是较小的序号。

代码

接下来奉上我的代码,有点丑,还请见谅。
CPP
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;

int n,m,head[N],cnt=-1;
int dfn[N],low[N],col[N],idx,tot;
//dfn表示dfs访问顺序,low表示能回溯到的最早节点,col表示强连通分量的编号,idx表示时间戳
//tot表示强连通分量的计数 
bool in[N];//节点数否在栈中 
stack <int> stk;
struct node{
	int u,v,next;
}e[N*4];

int read(){
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
	return x*f;
}

void adde(int u,int v){
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}

void tarjan(int u){
	dfn[u]=low[u]=++idx;
	stk.push(u);
	in[u]=true;
	for (int i=head[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if (!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if (in[v])
			low[u]=min(low[u],dfn[v]);
	}
	if (dfn[u]==low[u]){
		tot++;
		int v;
		while (1){
			v=stk.top();
			stk.pop();
			in[v]=false;
			col[v]=tot;
			if (v==u)
				break;
		}
	}
}

int main(){
	memset(head,-1,sizeof head);
	n=read(),m=read();
	for (int i=1;i<=m;i++){
		int x=read(),a=read(),y=read(),b=read();
		int u=(x<<1)|(a^1);//x!=a的状态 
		int v=(y<<1)|b;//y==b的状态 
		adde(u,v);
		u=(x<<1)|a;//x==a的状态 
		v=(y<<1)|(b^1);//y!=b的状态 
		//因为如果a的状态不满足的话,那么b的状态一定满足,反向同理 
		adde(v,u);
	}
	for (int i=2;i<=(n<<1|1);i++)
		if (!dfn[i])
			tarjan(i);
	for (int i=1;i<=n;i++){
		int f_node=i<<1;//x为假的节点 
		int t_node=i<<1|1;//x为真的节点 
		if (col[f_node]==col[t_node]){
			//如果这个节点又为假又为真,那么就矛盾
			//比如 x1=1 或 x1=0 就是矛盾的 
			cout <<"IMPOSSIBLE"<<endl;
			return 0;
		}
	}
	cout <<"POSSIBLE"<<endl;
	for (int i=1;i<=n;i++){
		int f_node=i<<1;//同上 
		int t_node=i<<1|1;
		if (col[f_node]<col[t_node])
		//拓扑排序是小序号有较大的拓扑序,大序号反之,为了避免后效性,所以选择较大的拓扑序也就是较小的序号 
			cout <<0<<" ";
		else
			cout <<1<<" ";
	}
	cout <<endl;
	return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...