2_sat 学习笔记

2_sat 学习笔记

SAT 是适定性(Satisfiability)问题的简称,这里只讲 2_sat 是因为这个问题不是npc 问题 是可解的。

一般来说,就是设置两个变量,分别是 a 和 $\lnot a $ 这里的 a 是一个表达式,比如这个人是否选,这个值是否小于一个值之类。通常,这一类题用一个表达式的真假可以唯一确定地分辨一个另一个表达式的真假。这就是 2_sat 问题。

先来一道例题:[P5782 POI 2001] 和平委员会 - 洛谷 (luogu.com.cn)

这题每一个条件都可以通过选第一个代表所不能选的另一个代表,也就是选了 a 必须选 ¬b,这就可以建模成 2_sat 问题。

#include<cstdio>
#include<vector>
#define pii pair<int,int>
using namespace std;
const int N=16000+5,M=2E5+5;
int n,m,low[N],dfn[N],cnt,st[N],top,flag[N],vis[N],id[N],sc;
vector<int>e[N];
void tomin(int &x,int y){if(y<x)x=y;
}
inline void tarjan(int rt) {
	vis[rt]=1;
	dfn[rt]=low[rt]=++cnt;
	st[++top]=rt,flag[rt]=1;
	for(int v:e[rt]) {
		if(!dfn[v]) {
			tarjan(v);
			tomin(low[rt],low[v]);
		}
		if(flag[v])
			tomin(low[rt],dfn[v]);
	}
	if(dfn[rt]==low[rt]) {
		++sc;
		id[rt]=sc;
		int y;
		do {
			y=st[top--];
			id[y]=sc;
			flag[y]=0;
		} while(y!=rt);
	}
}
pii c[M<<1];
int p[N],L[N],R[N];
void dfs(int rt ,int fa){
	L[rt]=++cnt;
	for(int p:e[rt]){
		dfs(p,rt);
	}
	R[rt]=cnt;
}
int res[N]; 
int fr(int x){
	if(x&1)return x+1;
	else return x-1;
}
signed main(){
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=2*n;i++)e[i].clear(),vis[i]=0,dfn[i]=0,vis[i]=0,p[i]=0,L[i]=0,R[i]=0;
		top=0,cnt=0,sc=0; 
		for(int i=1,a,b;i<=m;i++){
			scanf("%d%d",&a,&b);
			e[a].push_back(fr(b));
			e[b].push_back(fr(a));
		}
		for(int i=1;i<=2*n;i++)if(!vis[i])tarjan(i);
		//a-> !a && !a ->a false 
		int ans=0;
		for(int i=1;i<=n;i++)
			if(id[2*i]==id[2*i-1])ans=1;
		if(ans)printf("NIE\n");
		else {
			for(int i=1;i<=n;i++){
				if(id[i*2]<id[(i*2)-1]){
					printf("%d\n",i*2);
				} 
				else printf("%d\n",i*2-1);
			}
		}
 	}
	return 0;
}

3683 -- Priest John's Busiest Day (poj.org)

这题很有意思,乍一看很难想到 2_sat 问题,但是这个题只能选时间段的两端,那我们就可以用2_sat解决了,可以 n2 的判断每两个区间是否矛盾。

#include<cstdio>
#include<vector>
#include<string>
#include<iostream>
using namespace std;
const int N=2005;
int n;
int a[N],b[N],d[N];
int vis[N],dfn[N],low[N],flag[N],st[N],id[N],sc,cnt,top;
string s;
vector<int>e[N];
void tomin(int &x,int y){
	if(x>y)x=y;
}
inline void tarjan(int rt) {
	vis[rt]=1;
	dfn[rt]=low[rt]=++cnt;
	st[++top]=rt,flag[rt]=1;
	for(int i=0;i<e[rt].size();i++) {
		int v=e[rt][i];
		if(!dfn[v]) {
			tarjan(v);
			tomin(low[rt],low[v]);
		}
		else if(flag[v])
			tomin(low[rt],dfn[v]);
	}
	if(dfn[rt]==low[rt]) {
		++sc;
		id[rt]=sc;
		int y;
		do {
			y=st[top--];
			id[y]=sc;
			flag[y]=0;
		} while(y!=rt);
	}
}
bool solve(int l1,int r1,int l2,int r2){
	if(l1>l2)swap(l1,l2),swap(r1,r2);
	if(r1>l2)return true;
	return false;
}
signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		cin>>s;
		a[i]=((s[0]-'0')*10+(s[1]-'0'))*60+(s[3]-'0')*10+s[4]-'0';
		cin>>s;
		b[i]=((s[0]-'0')*10+(s[1]-'0'))*60+(s[3]-'0')*10+s[4]-'0';
		scanf("%d",&d[i]);
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			//有交集
			if(solve(a[i],a[i]+d[i],a[j],a[j]+d[j])){
				e[i].push_back(j+n),e[j].push_back(i+n) ;  
			} //11
			if(solve(a[i],a[i]+d[i],b[j]-d[j],b[j])){
				e[i].push_back(j),e[j+n].push_back(i+n) ; 
			}//12
			if(solve(b[i]-d[i],b[i],a[j],a[j]+d[j])){
				e[i+n].push_back(j+n),e[j].push_back(i) ; 
			}//21
			if(solve(b[i]-d[i],b[i],b[j]-d[j],b[j])){
				e[i+n].push_back(j),e[j+n].push_back(i) ;
			}//22
		}
	}
	for(int i=1;i<=2*n;i++)if(!vis[i])tarjan(i);
	for(int i=1;i<=n;i++){
		if(id[i]==id[i+n]){
			puts("NO");
			return 0;
		}
	}
	puts("YES");
	for(int i=1;i<=n;i++){
		if(id[i]<id[i+n]){
			printf("%02d:%02d %02d:%02d\n",a[i]/60,a[i]%60,(a[i]+d[i])/60,(a[i]+d[i])%60);
		}
		else {
			printf("%02d:%02d %02d:%02d\n",(b[i]-d[i])/60,(b[i]-d[i])%60,b[i]/60,b[i]%60);
		}
	}
	return 0;
}

Problem - 1697F - Codeforces

这题是一道难题。

这题其实很容易幻视成差分约束,但是第一个条件马上打消了我的臆想,但是可以2_sat 吗?

如果设 a[i][j] 表示 xi=j 的可能与否,这样的话就是 k_sat 了,很明显不能做,但是,我们看到了这个 k 很小,就是我们可以把 a[i][j] 表示成 xij 这样的话就可以枚举 ai,aj 的大小来进行连边了。

进行推理就可以进行一些连边,举个例子——aix 说明了 a[i][x]a[i][x+1],¬a[i][x1]¬a[i][x] 同理可以推出其他的两个要求的连边方式。

posted @   hnczy  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示