今天我自己主要把昨天网络流的东西再做一做,下午讲课讲
2−SAT
问题
我打算将感悟分成两半,第一部分主要写写昨天没写完的Dinic算法,第二部分主要讲讲
2−SAT
和相关的题目
Part1
EK算法应该还记得吧,时间复杂度比较高
Dinic算法基本上可以说是对EK算法的一个优化吧
在讲Dinic算法之前,先说一下最短增广路算法
概念
网络的层次:对于点
i
,记
li
表示这个点的层数,那么,
li=
从源点
S
到点
i
最少经过的边数
特别地,
lS=0
对网络分层就是将网络中所有点的层数求出
层次网络:对于其中任意一条弧
(u,v)
,都满足
lu+1=lv
思想
每一次在层次网络中找到一条含弧数较少的增广路进行增广
步骤:
1.初始化
2.构造层次网络,看汇点是否在网络中。如果汇点在网络中,在层次网络中一直增广,直到找不到增广路。增广后,在层次网络中去掉饱和的弧。否则结束。
增广到边
(u,v)
的时候,如果满足
lu+1=lv
,就增广这条边
复杂度
建立层次网络需要用到bfs,最多会出现
O(n)
个层次网络,bfs复杂度为
O(m)
,建立复杂度为
O(nm)
每条边只会被增广一次,所以最多会增广
m
次。用bfs来进行增广,时间为
O(m)
。调整每条边的流量需要消耗
O(n)
的时间。增广复杂度为
O(m(n+m))=O(m2)
总复杂度为
O(nm2)
这个算法的时间复杂度并没有多大的保证,一不小心就会T,建议大家还是用下面要说的Dinic算法
Dinic算法
为什么前面的算法时间复杂度特别高?因为每一次只增广一次,太浪费了
所以Dinic算法就用dfs多次增广,效率就自然上来了
算法的大致流程:
前两步同上面算法
3.用dfs进行增广
时间复杂度分析:
建立层次网络依然是
O(nm)
的
修改增广路最多增广
m
次,每一次调整流量时间为
O(n)
,增广后后退的时间也为
O(n)
增广路长度最大为
n
,每一次增广后要删除一条路径的最后一个点,最坏后退
n
次
所以dfs的时候复杂度为
O(nm)
所以最后总时间复杂度为
O(n2m)
相对于前面的算法显然要好了很多啊
发一下Dinic的代码
bool bfs(int s,int t){//bfs为寻找增广路的过程,true为存在增广路
memset(l,0,sizeof(l));l[s]=1;//l数组表示每个点的层数
queue<int> q;q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
if(x==t) return true;//如果遇到汇点,说明存在增广路径
for(int p=e[x].next;p;p=e[p].next){
int k=e[p].num,c=e[p].c;
if(!l[k]&&c){
l[k]=l[x]+1;
q.push(k);
}
}
}
return false;
}
int dfs(int s,int t,int maxf){//maxf表示当前的流量上界是多少
if(s==t) return maxf;
int ret=0;//ret表示这个层次网络的最大流
for(int p=e[s].next;p;p=e[p].next){
int k=e[p].num,c=e[p].c;
if(l[s]+1==l[k]&&c){
int Min=min(maxf-ret,c);//取min应该比较好理解,因为小的可以流到大的,但大的不一定能流到小的
c=dfs(k,t,Min);
e[p].c-=c;e[p^1].c+=c;
ret+=c;
if(ret==maxf) return ret;//如果漫流,就不可能更多了,直接退出
}
}
return ret;
}
int Dinic(int s,int t){
int maxflow=0;
while(bfs(s,t)) maxflow+=dfs(s,t,INT_MAX);//求解最大流
return maxflow;
}
代码也比较简短,掌握了就会好很多
我曾经提到过二分图最大匹配可以用网络流做,时间复杂度比匈牙利要好得多
其实Dinic复杂度比
O(n2m)
要低,这是上界,并且很松
但匈牙利唯一优势在于可以求出字典序最小的最大匹配
曾经有人用Dinic跑
n=105,m=106
的最大流,就是过了(数据随机)
说明这个算法的效率还是很高的,要好好写
最大流一般都会用Dinic来写
Part2
下面我们换个思路,讲讲 2−SAT 问题
概念
SAT
问题:一些由布尔值组成的关系的集合
2−SAT
问题:由两个布尔值组成的关系的集合
给你一堆关系,最后问你能否满足所有的关系??
来个例题看看,顺便讲一下这类问题的方法
例题
和平委员会(Poi 0106)
某国有
n
个党派,每个党派有2个代表
要建立一个和平委员会,并要满足下面两个条件:
1.每个党派只有一个代表
2.不能出现两个代表不和的情况(不和的关系告诉你)
第
i
个党派的两个代表编号分别为
2i+1
和
2(i+1)
问能否使每个党派都有代表在委员会里?
求出方案(其实不重要)
n≤8000,m≤20000
做法
我们先假设第
i
个党派的一个代表为
i
,另一个为
i′
如果出现某两个代表
i
和
j
不和,那么选择了
i
,就要选择
j′
;选择了
j
,就要选择
i′
那么我们可以连边
(i,j′)
和
(j,i′)
这两条边对称(很关键)
举个例子
假设有4个党派,不和关系为:1和4,2和3,3和7
考虑下面的构图:
假设先选1,3,8必须选,2,4,7不能选,5,6随意
这样建图就比较巧妙啦
为什么会出现矛盾呢?(这个点必须选但是有不能选……好尴尬)
那么我们就会有一个比较暴力的做法:
枚举每一对没有定下来怎么选的
(i,i′)
,任选一个,开始推导其他的种种关系
如果都出现矛盾,那么就无解
正确性?前面的决策并不会影响
(i,i′)
时间复杂度? 最坏
O(nm)
有没有优化?
注意,
2−SAT
问题主要的优化是看它的对称性
显然暴力对于对称性不怎么会用……
对于上面的例子,发现1,3构成一个环,2,4构成一个环
说明1,3怎么取是固定的,2,4怎么取也是固定的
说明一个环里怎么取肯定是固定的
可以用tarjan算法缩点,将强连通分量缩成一个点,并标记好原图中每一个点属于哪一个强联通分量,建立一个新图
所以新图就变成了一个DAG(有向无环图)
如果出现某一对
(i,i′)
属于同一个点,肯定无解
否则用拓扑排序自底向上来求,肯定存在可行解
正确性证明?
显然,原图是具有对称传递性的(
i
->
k
,那么
k′
->
i′
“->”表示传递的关系)
根据上面的对称传递性,环是对称的
新图也同样存在对称性
定义
Si
表示第
i
个环
对于任意一对
(Si,Si′)
,
Si
的后代节点和
Si′
的前代祖先也不能选
如果选择了
Si
,那么对于所有边
(Si,Sj)
,
Sj
必须选
因为新图依然具有对称性,那么
Si′
不可选(
Si′
表示
Si
的对称点)。所以
Si′
的前代祖先也不能选,假设我们删除了这些点
因为前代祖先的某种对称关系(上面提到了),所以这样删除并没有矛盾
所以一定可以构造出一种可行解(证毕)
相关细节:拓扑排序的时候,因为新图变成了两个部分,并且对称,所以要两边同时做
时间复杂度?
O(m)
2−SAT
问题必须要满足对称性(无向图显然没有问题,有向图必须满足对称)
看一道比较高端的 2−SAT 题
高端例题
游戏(NOI2017 day2T1)
有一个字符串长度为
n
,每一位有四种字符(分别为小写字母a,b,c,x)。现在每一位可以放大写字母A,B,C。在某位字符为x的位置上三种都能放,其他的不能出现大小写对应的情况(比如a号位不能选A)
还有
m
个特殊的约束条件,形如
(i,hi,j,hj)
。表示如果在
i
号位选择
hi
字符,那么
j
号位必须选择
hj
字符
求出一种合法的方案,如果无解,输出-1
如果有多组解,输出任意一种
n≤5∗104,m≤105,d=|′x′|≤8
2−SAT
模型?
一看难道不是
3−SAT
么,NP问题……
出题人出错了????
因为每一位只能选两种,所以不可能是
3−SAT
问题
图满足对称性???
假设字符串为
abc
,约束条件为
(2,A,3,B)
那么二号位的A连上三号位的B(有向边)
因为每一位都要取,所以三号位的A连上二号位的C
所以满足对称性
如果
d=0
?? 直接
2−SAT
问题即可(tarjan缩点+拓扑排序)
如果
d>0
??
时间复杂度
O(3d(n+m))
??
这显然T飞了吧……
如何优化????
考虑某一个点限制为不能取A,那么就可以取B,C。同理,如果不能取B,那么只能取A,C
如果在不能取A的情况下没有可行解,那么就不能取B,C
再看能不能取A,即看不能取B的情况
若没有可行解,说明A也不能取
所以无解,只需枚举两种即可
时间复杂度
O(2d(n+m))
我们就成功地解决了一个NOI问题啦!!!
激动的心情不禁溢于言表……
当时讲题人说如果这题考场上A掉,笔试满分,其他题暴力基本就可以拿Cu了
表示我连胸牌都没有,还说啥……
2−SAT
的具体代码我还没有写,最近有时间会写写
代码可能比较丑陋,就不敢发上来了……
讲题人说
2−SAT
问题比较明显,基本想到了对称性就可以A掉了……
表示我这种连胸牌选手都算不上的人能信么……