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;
}
这题是一道难题。
这题其实很容易幻视成差分约束,但是第一个条件马上打消了我的臆想,但是可以2_sat
吗?
如果设 a[i][j] 表示 xi=j 的可能与否,这样的话就是 k_sat
了,很明显不能做,但是,我们看到了这个 k
很小,就是我们可以把 a[i][j] 表示成 xi≤j 这样的话就可以枚举 ai,aj 的大小来进行连边了。
进行推理就可以进行一些连边,举个例子——ai≠x 说明了 a[i][x]→a[i][x+1],¬a[i][x−1]→¬a[i][x] 同理可以推出其他的两个要求的连边方式。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现