专栏文章
2-SAT略解
算法·理论参与者 33已保存评论 34
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 34 条
- 当前快照
- 1 份
- 快照标识符
- @mhz5rpvd
- 此快照首次捕获于
- 2025/11/15 01:55 4 个月前
- 此快照最后确认于
- 2025/11/29 05:24 3 个月前
竟然没有的百度词条 _(:з」∠)
是一种特殊的逻辑判定问题
emmmm先看一道题:
众所周知,有很多的Au不然怎么叫Aufun
每天最大的乐趣就是把这些Au翻过来翻过去
现在你已经知道m条关于这些Au的信息,每条信息告诉你标号为 Au正/反的时候标号为 的Au不能为正/反
想问问你是否有一种情况ta 的Au满足这些条件,并让你随便构造出一种来
发现每块Au(点)都有两种状态(正/反)
我们想到每个点可以拆开成两个点,分别代表着一种状态,表示这个点状态为反,表示这个点状态为正
emmmm有点了,我们可不可以通过连边来表示点与点之间的关系呢?
如果选了点就必须选点,就从出发连一条单向边到
这样两种状态之间,正面必须反面就可以表示成

如图,如果按照刚才的方法连边,应该是正面反面,反面反面,反面正面,正面正面,反面正面,反面正面
等等反面正面是什么鬼?
就是反面就必须正面,这样的事情根本不可能你给我搞一个试试,所以必须正面
发现点与点之间是单向边,那么我们连着连着就可能出现强连通分量
有什么影响吗?

这样构成的一张图,可以看出在一个强连通分量中,在一个强连通分量说明选了其中一个点其他在同一强连通分量中的点都必须选
这样选就必须选,选也必须选,这肯定不成立QAQ
所以我们可以通过找强连通分量来判断是否存在可行方案
复杂度:(为连边数量)
因为连表示选必须选,所以的拓扑序肯定大于
注意这里是选就必须选,但是选不一定选
所以对于每个点的两种状态,可以选拓扑序大的,舍弃掉另一个(选一个就不能选另一个了)
每个点都判断与对立点谁的拓扑序大
注意这里可以不用再搞一遍拓扑排序,因为求强连通分量每个点所在的强连通分量编号可以代替拓扑序
编号和拓扑序是相反的,即要每两对点找编号小的
输出每个点状态就行了
两个点的关系还有很多,列举几个(这里存在和不存在是一个点的两种状态):
不能同时存在:
必须同时存在:
必须存在其中一个:
不能存在:
上面的输出方案是随意输出一种即可,但是要是要求输出字典序最小的答案呢?
也是很好解决的,即可
点从枚举,如果这个点能选,就将ta相连的点都选上,就将ta相连的点的相连的点都选上,就将...
如果某个点发生冲突了(对立点被选了),这个点就不能选,与ta相连的点不能选,与ta相连的点的相连点不能选,与ta...
这样最后被选中的点就是这字典序最小的解了,复杂度:
几道例题
上面的第三题,是按和不能同时存在建边的
但是通过拓扑序很难判断三种状态
考虑当前点的某个状态对应的点,表示必须选这个状态,如果对立状态也被选中,说明这个点不能选,否则说明这个点可以被选
这样一个点的两个状态所对应的点
如果两个状态都能被选,说明这个点可选可不选;如果两个状态都不能选,说明没法构造出可行的方案,输出;如果某个状态可选,对立状态不可被选,说明这个点必须选这个状态
部分代码
CPPvoid dfs(int x)//use表示是否选这个点
{
use[x]=1;
for(int i=h[x];i;i=c[i].x)
if(!use[c[i].y]) dfs(c[i].y);
}
bool look(int x)
{
memset(use,0,sizeof(use));
dfs(x);//选择这个点和ta后面的点
for(int i=1;i<=n;++i)
if(use[i]&&use[i+n]) return 0;//判断方法还是一样的,如果两个状态都被选了说明不可行
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
//构造图过程略去
for(int i=1;i<=n;++i)
{
bool d1=look(i),d2=look(i+n);//判断两个状态
if(!d1&&!d2) return printf("IMPOSSIBLE"),0;
if(d1&&d2) ans[i]='?';
if(d1&&!d2) ans[i]='N';
if(!d1&&d2) ans[i]='Y';//记录每种情况
}
for(int i=1;i<=n;++i)
printf("%c",ans[i]);
return 0;
}
再看一下第二道题水到200行
很显然场地分别对应两种车,符合的要求
这个是按必须同时存在建边的,比如说为,则将场地的状态场地的状态
但是场地对应三种车怎么办呢?
发现场地的个数很少,最大才个
所以考虑枚举场地的状态,比如这次可以让场地对应车(就成了场地),下次对应车(就成了场地),再下次对应车(就成了场地)
这样复杂度是,仍然过不了这道题
发现对应AC车的情况在前面两个枚举中都出现了,也就是说前面两个枚举把所有情况都包括了
所以只用枚举前两个就行了,复杂度
部分代码
CPPbool look()//枚举x状态进行判断是否可行
{
memset(h,0,sizeof(h));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
num=0,Col=0;
for(int i=1;i<=m;++i)
{
if(S[s[i].i]==s[i].hi+32) continue;//S是场地字符串,s是给定4元组
int tt=s[i].hi+32==pos[0][s[i].i]?s[i].i:s[i].i+n;//pos为每个点代表的两个状态
if(S[s[i].j]==s[i].hj+32) add(tt,re[tt]);//re为对立点
else
{
int ttt=s[i].hj+32==pos[0][s[i].j]?s[i].j:s[i].j+n;
add(tt,ttt),add(re[ttt],re[tt]);
}
}
for(int i=1;i<=2*n;++i)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i)
if(col[i]==col[i+n]) return 0;
return print(),1;
}
注意:中间那里是判断处是否能被选(这里能被选的意思是没有条件限制的处场地适合开这个车)
如果能被选,就按必须同时存在连边;否则说明不能选这个四元组,就按不能存在连边
相关推荐
评论
共 34 条评论,欢迎与作者交流。
正在加载评论...