2-SAT

算法理解

首先我们要了解一种很典型的图论建图思想,如果a一定b,那么就将a->b建一条边,然后如果在一个强连通分量中逻辑关系冲突了,则不满足,反之则满足

2-sat与扩展域并查集的区别

2-sat有向图,扩展域并查集无向图
2-sat若a则b,扩展域并查集若a则b若b则a

2-sat的具体实现


我们将或转化为若则的逻辑关系,因为对于一个 \(x_i\) 只有可能有真或假的两种关系,所以对于一个约束条件 \(x_1\) 为真或 \(x_2\)为假,就可以转化为若 \(x_1\) 为假,则 \(x_2\)为假和若 \(x_2\) 为真,则 \(x_1\)为真
像这样我们就把一种逻辑关系转化为一条边了,知周所众,同一个强连通分量的点可以互相到达,所以同一个强连通分量中若出现冲突,就像同时出现 \(x_1\) 为假和真,就一定不合法,反之则一定合法,我们将所有点进行一个拓扑排序,然后拓扑序更大那个就是合法解,因为其出度更小,所以约束别的条件就更少,所以更优

2-sat为什么一个强连通分量中没有冲突则一定有合法解呢?

首先在强连通分量中,一个真可以推出一个假,而一个假也可以推出一个真,所以不成立,然而缩完点之后只有像一个可以推出一个真这种条件,那么我们只需要取真就行,因为真推不出假,所以真一定拓扑序更大,所以取真一定最优

优化

考虑强连通分量的标号顺序恰好是拓扑序的反序,因为在dfs搜索树上,是从前往后搜的,而缩点是从后向前搜的,缩完点后,返祖边不复存在,树边从编号大的指向小的,横叉边也是从还没缩完点的点指向已经缩过点的强连通分量,所以必然编号越小的拓扑序越大,因为指的边多

赋值

将强连通分量值较小的作为答案,原因前文已经解释了

例题

P4782 【模板】2-SAT

#include<bits/stdc++.h>
using namespace std;
const int N=4e6+5;
int n,m,u,v,a,b,colnum,tot,cnt,l;
int col[N],low[N],dfn[N],s[N],vis[N];
vector<int>e[N];
void add(int x,int y){
	e[x].push_back(y);
}
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	s[++l]=x;
	for(int i:e[x]){
		if(!dfn[i])  tarjan(i);
		if(!vis[i])  low[x]=min(low[x],low[i]);
	}
	if(low[x]==dfn[x]){
		colnum++;
		while(s[l]!=x){
			col[s[l]]=colnum;
			if(s[l]>n){
				if(vis[s[l]-n]==colnum)  cnt=1;
			}
			if(s[l]<=n){
				if(vis[s[l]+n]==colnum)  cnt=1;
			}
			vis[s[l]]=colnum;
			l--;
		}
		col[s[l]]=colnum;
		if(s[l]>n){
			if(vis[s[l]-n]==colnum)  cnt=1;
		}
		if(s[l]<=n){
			if(vis[s[l]+n]==colnum)  cnt=1;
		}
		vis[s[l]]=colnum;
		l--;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&u,&a,&v,&b);
		if(a&&b){
			add(u+n,v);
			add(v+n,u);
		}
		if(!a&&!b){
			add(u,v+n);
			add(v,u+n);
		}
		if(a&&!b){
			add(u+n,v+n);
			add(v,u);
		}
		if(!a&&b){
			add(u,v);
			add(v+n,u+n);
		}
	}
	for(int i=1;i<=2*n;i++){//注这里n要*2
		if(!dfn[i])  tarjan(i);
	}
	if(cnt){
		printf("IMPOSSIBLE");
		return 0;
	}
	printf("POSSIBLE\n");
	for(int i=1;i<=n;i++){
		if(col[i]>col[i+n]){
			printf("0 ");
		}
		else{
			printf("1 ");
		}
	}
}

P4171 [JSOI2010] 满汉全席

类模板

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int K,n,m,a,b,u,v,tot,l,colnum,cnt;
int dfn[N],low[N],vis[N],s[N];
vector<int>e[N];
void add(int x,int y){
	e[x].push_back(y);
//	printf("%d %d\n",x,y);
}
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	s[++l]=x;
	for(int i:e[x]){
		if(!dfn[i])  tarjan(i);
		if(!vis[i])  low[x]=min(low[x],low[i]);
	}
	if(low[x]==dfn[x]){
		colnum++;
		while(s[l]!=x){
			if(s[l]>n){
				if(vis[s[l]-n]==colnum)  cnt=1;
			}
			if(s[l]<=n){
				if(vis[s[l]+n]==colnum)  cnt=1;
			}
			vis[s[l]]=colnum;
			l--;
		}
		if(s[l]>n){
			if(vis[s[l]-n]==colnum)  cnt=1;
		}
		if(s[l]<=n){
			if(vis[s[l]+n]==colnum)  cnt=1;
		}
		vis[s[l]]=colnum;
		l--;
	}
}
int main(){
	scanf("%d",&K);
	while(K--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			string s1,s2;
			cin>>s1>>s2;
			if(s1[0]=='m')  a=1;
			else  a=0;
			if(s2[0]=='m')  b=1;
			else  b=0;
			u=0,v=0;
			int t=1;
			while(s1[t]){
				u=u*10+(s1[t]-'0');
				t++;
			}
			t=1;
			while(s2[t]){
				v=v*10+(s2[t]-'0');
				t++;
			}
			// printf("u=%d a=%d v=%d b=%d\n",u,a,v,b);
			if(a&&b){
				add(u+n,v);
				add(v+n,u);
			}
			if(!a&&!b){
				add(u,v+n);
				add(v,u+n);
			}
			if(!a&&b){
				add(u,v);
				add(v+n,u+n);
			}
			if(a&&!b){
				add(u+n,v+n);
				add(v,u);
			}
		}
		for(int i=1;i<=2*n;i++){
			if(!dfn[i]){
				tarjan(i);
			}
		}
		if(!cnt){
			printf("GOOD\n");
		}
		else{
			printf("BAD\n");
		}
		colnum=cnt=l=tot=0;
		for(int i=1;i<=2*n;i++)  dfn[i]=low[i]=vis[i]=s[i]=0;
		for(int i=1;i<=2*n;i++){//这里也要n*2
			e[i].clear();
		}
	}
}

P3825 [NOI2017] 游戏

一眼:3-sat好耶!

为什么3-sat不可做?

因为2-sat
基于一个事实:一个节点如果不a则b,所以只有两种状态可以选择,然而3-sat,如果不a到底是b还是c呢?

做法:

x如何转化?注意到 \(d<=8\) 暴力枚举每个x是a还是b或c, \(O(3^d)\) 题解说过不了,题解又说可以只枚举两次 \(O(2^d)\) 就可以解决,为什么呢?
考虑你每次枚举的是不选的车,不选a选b,c,不选b选a,c,但是每条赛道只用有一辆车,我们相当于已经涵盖了选a,b,c三种情况了

2-sat变式

2-sat是类似与a或b的问题的,这里是若a则b的问题,哦,那不就更简单了吗,直接做就做完了,然后就喜提20分然后以为写挂了一直调调不出来,考虑这样一组情况

按照2-sat进行赋值,显然会选择1,2,但就不满足条件,因为选2一定选3
so
我们重新加边考虑若2为真则1为假,若1为真则2一定不为真,就有 \(a->b则!b->!a\)
于是这道题就做完了

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,d,ntnum,m,l,tot,colnum,cnt,cn;
char s[N],c1[N],c2[N];
int track[N],nt[N],ti[N],tj[N],col[N],st[N],dfn[N],low[N];
vector<int>b[N];
int giveid(int tx,char x){//返回1/0
	int y,acr=0;
	if(x=='A')  y=1;
	if(x=='B')  y=2;
	if(x=='C')  y=3;
	for(int i=1;i<y;i++){
		if(track[tx]!=i)  acr++;
	}
	if(track[tx]==y)  return -1;
	return acr;
}
void print(int tx,int x){
	int y=0;
	for(int i=1;i<=3;i++){
		if(track[tx]==i)  continue;
		if(y==x)  printf("%c",'A'+i-1);
		y++;
	}
}
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	st[++l]=x;
	for(int i:b[x]){
		if(!dfn[i])  tarjan(i);
		if(!col[i])  low[x]=min(low[i],low[x]);  
	}
	if(low[x]==dfn[x]){
		colnum++;
		while(st[l]!=x){
			col[st[l]]=colnum;
			if(st[l]<=n){
				if(colnum==col[st[l]+n])  cnt=1;
			}
			else{
				if(colnum==col[st[l]-n])  cnt=1;
			}
			l--;
		}
		col[st[l]]=colnum;
		if(st[l]<=n){
			if(colnum==col[st[l]+n])  cnt=1;
		}
		else{
			if(colnum==col[st[l]-n])  cnt=1;
		}
		l--;
	}
}
void add(int x,int y){
	b[x].push_back(y);
//	printf("%d %d\n",x,y);
}
void addedge(){
	l=0,tot=0,colnum=0,cnt=0;
	for(int i=1;i<=2*n;i++){
		b[i].clear();
		col[i]=st[i]=dfn[i]=low[i]=0;
	}
	for(int i=1;i<=m;i++){
		int u=ti[i],v=tj[i];
		int a=giveid(u,c1[i]),b=giveid(v,c2[i]);
		if(a==-1)  continue;
		if(b==-1){
			if(!a)  add(u,u+n);
			else  add(u+n,u);
			continue;
		}
		if(a&&b){
			add(u+n,v+n);
			add(v,u);
		}
		if(!a&&!b){
			add(u,v);
			add(v+n,u+n);
		}
		if(a&&!b){
			add(u+n,v);
			add(v+n,u);
		}
		if(!a&&b){
			add(u,v+n);
			add(v,u+n);
		}
	}
	for(int i=1;i<=2*n;i++){
		if(!dfn[i])  tarjan(i);
	}
	if(!cnt){
		cn=1;
		for(int i=1;i<=n;i++){
			if(col[i]<col[i+n])  print(i,0);
			else  print(i,1);
		}
	}
}
void dfs(int x){
	if(cn)  return;
	if(x>d){
	//	printf("num=%d %d\n",track[1],track[2]);
		addedge();
		return;
	}
	track[nt[x]]=1;
	dfs(x+1);
	track[nt[x]]=2;
	dfs(x+1);
	track[nt[x]]=0;
}
int main(){
	scanf("%d%d",&n,&d);
	cin>>(s+1);
	for(int i=1;i<=n;i++){
		if(s[i]=='x'){
			nt[++ntnum]=i;
			track[i]=0;
		}
		if(s[i]=='a'){
			track[i]=1;
		}
		if(s[i]=='b'){
			track[i]=2;
		}
		if(s[i]=='c'){
			track[i]=3;
		}
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d %c%d %c",&ti[i],&c1[i],&tj[i],&c2[i]);
	}
	if(d)  dfs(1);
	else  addedge();
	if(!cn){
		printf("-1");
	}
}
posted @ 2024-10-15 11:44  daydreamer_zcxnb  阅读(6)  评论(0编辑  收藏  举报