2018暑假集训专题小结 Part.6
总
DP的优化——四边形不等式优化
插头DP浅析
四边形不等式优化
我们在做各种动态规划的题目时,经常遇到类似于下面的方程:
f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j));
我们说,w(i,j)为附加函数。比如上面,就是一个附加的价值。(根据题意而异)
但是,上述方程直接暴力做是n^3的。
那么我们就可以拿出优化。
一个定义:s[i,j]表示当前f[i,j]的最优决策点为s[i,j]
首先,我们先看看w(i,j)满足什么情况的时候是满足四边形不等式——
1、区间包含的单调性:对于任意a ≤ b < c ≤ d,有w(b,c)≤w(a,d),那么说明w具有区间包含的单调性。
2、四边形不等式:对于任意a ≤ b < c ≤ d,有w(a,c)+w(b,d)≤w(b,c)+w(a,d),我们称函数w满足四边形不等式。
于是,我们就来看看三个主要的定理:
定理一:
对于一个方程:f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j));
若w(i,j)满足四边形不等式与区间包含的单调性。
那么f[i,j]也满足。
边界:
f[i,i]:=0;
f[i,j]:=0;(i>j)
————————————————————————
证明:
区间包含单调性:显然。
四边形不等式:
也就是说我们要证明:对于任意a ≤ b ≤ c ≤ d ,有f[a,c]+f[b,d]≤f[b,c]+f[a,d]。
分类讨论。
当a=b或c=d时,f[a,c]+f[a,d]≤f[a,c]+f[a,d]
f[a,c]+f[b,c]≤f[b,c]+f[a,c]
得证
————————————————————————
于是现在,我们假设f[a,d]的子区间都满足四边形不等式。
当a < b = c < d
那么原式变成:f[a,b]+f[b,d]<=f[a,d]
我们设k=s[a,d]
于是再分类讨论:
1、当k < b
得证。
2、当k>=b
得证。
————————————————————————
当a < b < c < d
设y=s[b,c],z=s[a,d]
继续分类讨论:
1、当a < b <= y <= z
得证。
2、当a < b <= z <= y
那么就z拆f[a,c],y拆f[b,d]。过程和上面差不多
得证。
3、当a <= z < b <= y
同样的z拆f[a,c],y拆f[b,d]。
得证。
4、当a <= z <= y < c < d
得证。
————————————————————————
所以,定理一:
对于一个方程:f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j);
若w(i,j)满足四边形不等式与区间包含的单调性。
那么f[i,j]也满足。
得证!
定理二:
对于一个方程:f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j);
若f[i,j]满足四边形不等式时。
那么s[i,j-1] <= s[i,j] <= s[i+1,j]
证明:
————————————————————————
先看s[i,j-1]<=s[i,j]
反证法:设k1=s[i,j],k2=s[i,j-1]。
且满足:k1 < k2 <= j-1 <= j
则:
两边同时加上
则:
移项:
这个式子什么意思?
对于f[i,j]来说,k1决策比k2更优,那么
于是
那么说明对于f[i,j-1]来说,k1决策比k2更优。
矛盾。
得证。
那么s[i,j]<=s[i+1,j]
反证法:同上。
————————————————————————
所以,定理二:
对于一个方程:f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j);
若f[i,j]满足四边形不等式时。
那么s[i,j-1] <= s[i,j] <= s[i+1,j]
得证!
定理三:
如果w(i,j)满足四边形不等式:w(a,c)+w(b,d)≤w(b,c)+w(a,d)
当且仅当w(i,j)满足:
w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)
证明:
————————————————————————
首先:i < i+1 <= j < j+1
a < b <= c < d
那么,我们发现,我们只需要证明i < i+k <= j < j+k即可
移项:
我们根据区间包含的单调性,得:
w(i,j)<=w(i+k,j)
w(i,j+1)<=w(i+k,j+1)
那么,当k=1时是
于是可以感受到:
相当于:
那么再移项回来,不就得证了吗?
————————————————————————
所以,定理三:
如果w(i,j)满足四边形不等式:w(a,c)+w(b,d)≤w(b,c)+w(a,d)
当且仅当w(i,j)满足:
w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)
得证!
上述三条定理如何用?
定理一+定理二
我们看看原来的式子:
f[i,j]:=min(f[i,k]+f[k+1,j]+w(i,j));
因为有s[i,j-1] <= s[i,j] <= s[i+1,j]
那么,我们的k就可以从s[i,j-1]到s[i+1,j]来枚举了。
然后,我们不直接枚举j的位置,我们枚举一个len表示i~j的区间大小。
那么j=i+len。
于是乎,我们的k就是从s[i,i+len-1]~s[i+1,i+len]枚举。
那么对于一个固定的len。
我们把i从小到大来枚举。
那么我们发现:
(s[2,1+len]-s[1,len])+(s[3,2+len]-s[2,1+len])+(s[4,3+len]-s[3,2+len])+……+(s[n+1,n+len]-s[n,n+len-1])
=s[n+1,n+len]-s[1,len]
然而这个结果是小于n的。
也就说明:
i这一维的枚举与k这一维的枚举的状态总数,是小于n的。
那么原来的n3就变成了n2.
很好地优化了时间。
定理三:
对于定理三,我们可以用很快速的方法来判断w是否满足四边形不等式。
原来的四次方复杂度w(a,c)+w(b,d)≤w(b,c)+w(a,d)
变成二次方复杂度w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)
很好地优化了时间+1。
例题
T1:HDU3506 Monkey Party
题意:
n只猴子围成一个圈。每只猴子有个a[i]
每次可以选择相邻的两只猴子,让它们以及它们的朋友们互相认识
时间是两帮猴子的a[i]之和。
求最少的时间让它们全部认识
n<=1000.
题解:
环形,那么就经典套路,复制一遍。
很明显,DP方程为:
设f[i,j]表示当前i~j的猴子已经认识。
f[i,j]:=min(f[i,k]+f[k,j+1]+w(i,j));
然后我们来分析一下w(i,j)
我们发现,这就是一个区间和。套到上面的式子看。
你就会发现这是满足的。
于是乎,我们就可以开始DP啦
代码:
uses math;
var
i,j,k,l,n,m,len,ans:longint;
a,sum:array[0..2001] of longint;
f:array[0..2001,0..2001] of longint;
begin
readln(n);
for i:=1 to n do
begin
read(a[i]);
a[n+i]:=a[i];
end;
m:=n+n;
for i:=1 to m do
begin
sum[i]:=sum[i-1]+a[i]
end;
fillchar(f,sizeof(f),127);
for i:=1 to m do
begin
f[i,i]:=0;
end;
for len:=1 to n-1 do
begin
for i:=1 to m do
begin
if i+len>m then continue;
j:=i+len;
for k:=i to j-1 do
begin
f[i,j]:=min(f[i,j],f[i,k]+f[k+1,j]+sum[j]-sum[i-1]);
end;
end;
end;
ans:=maxlongint;
for i:=1 to n do
begin
ans:=min(ans,f[i,i+n-1]);
end;
writeln(ans);
end.
加优化:
uses math;
var
i,j,k,l,n,m,len,ans:longint;
a,sum:array[0..2001] of longint;
f,s:array[0..2001,0..2001] of longint;
begin
readln(n);
for i:=1 to n do
begin
read(a[i]);
a[n+i]:=a[i];
end;
m:=n+n;
for i:=1 to m do
begin
sum[i]:=sum[i-1]+a[i]
end;
fillchar(f,sizeof(f),127);
for i:=1 to m do
begin
f[i,i]:=0;
s[i,i]:=i;
end;
for len:=1 to n-1 do
begin
for i:=1 to m do
begin
if i+len>m then continue;
j:=i+len;
for k:=s[i,j-1] to s[i+1,j] do
begin
if f[i,k]+f[k+1,j]+sum[j]-sum[i-1]<f[i,j] then
begin
f[i,j]:=f[i,k]+f[k+1,j]+sum[j]-sum[i-1];
s[i,j]:=k;
end;
end;
end;
end;
ans:=maxlongint;
for i:=1 to n do
begin
ans:=min(ans,f[i,i+n-1]);
end;
writeln(ans);
end.
这个可以看做是一个板题。
T2:POJ1160 Post Office
题意:有N个村庄,位置在p[1…n]上
要求建k个邮局使得每个村庄到最近的邮局的距离和最小。
n,k<=1000(原题是300)
题解:
首先排个序。
设f[i,j]表示已经建了i个邮局,j之前的村庄已经确定去哪个邮局的最小距离和。
那么方程显然:
f[i,j]:=min(f[i-1,k]+w(k+1,j));
w(i,j)表示一个邮局解决i~j之间村庄的最小费用。
我们就可以发现:
w(i,j)=w(i,j-1)+p[j]-p[(i+j)/2]
w(i,j)=w(i+1,j)-p[i]+p[(i+j)/2]
于是,w的区间包含单调性满足。
再来看看四边形不等式。
w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)
变成:
2w(i,j)+p[j+1]-p[(i+1+j+1)/2]+p[i]-p[(i+j)/2]<=2w(i,j)+p[j+1]-p[(i+j+1)/2]+p[i]-p[(i+j)/2]
化简:
-p[(i+j+2)/2]<=-p[(i+j+1)/2]
由于p是单调递增的,于是得证。
当做复习,我们再来证一遍f满足四边形不等式。
a ≤ b ≤ c ≤ d ,f[a,c]+f[b,d]≤f[b,c]+f[a,d]
分类:
当a=b或c=d时,显然。
当b=c时:
设k=s[a,d]
令k<=b.(k>b同理)
f[a,b]+f[b,d]
<=f[a-1,k]+w(k+1,b)+f[b-1,b-1]+w(b,d)//后面把k看成b-1.这样会更大。
<=f[a-1,k]+w(k+1,b)+w(b,d)//区间包含单调性
<=f[a-1,k]+w(k+1,d)
<=f[a,d]
得证。
当a < b < c < d 时:
设y=s[b,c];z=s[a,d]。
分类讨论:当y<=z时(其他情况类似)
则有a < b <= y <= z
f[a,c]+f[b,d]
<=f[a-1,y]+w(y+1,c)+f[b-1,z]+w(z+1,d)
<=f[a-1,z]+w(y+1,c)+f[b-1,y]+w(z+1,d)//子区间满足四边形不等式。
<=f[a,d]+f[b,c]
得证。
所以f就满足了四边形不等式。
于是就可以用n^2来做。
代码待填。
插头DP浅析
首先我们看看斯坦纳树。
斯坦纳:德国军人,二战时期党卫队副总指挥兼武装党卫军上将。
不是这个斯坦纳。
网上关于这玩意儿的资料不是很多,找资料找了好久,最后还是百度百科的比较完整。
斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。
但不好理解对吧?
应该就是没有多项式解法的最小生成树。这个斯坦纳树就可以用状压DP来求解。
其实,这不是重点。
是这道引入正题的题:
Description
Input
Output
输出一个整数,为矿工获得所有宝藏的最小代价。
Sample Input
2 2 2
10 9
10 10
10 1
10 10
1 1 1
1 2 2
Sample Output
30
Data Constraint
题解
这题就是一道经典的斯坦纳树的应用。
然而,斯坦纳树并不是那么地广泛适用。
于是,我们就来讲讲一个比斯坦纳树更为广用的算法:
插头DP
插头DP真名:连通性状态压缩的动态规划。
这个东西为什么叫插头,主要原因是这个DP是在一些很像电视插头的东西上求解问题。
我们来看看一道极为经典的例题:
HUD1693 EAT THE TREE
题意:给你一张n*m的图(如下图)
然后,每次要在一些格子上画一条线使得每个格子只经过一条线。并且线要全部覆盖。
求方案数。
我们接下来就看看如何用插头DP来求解。
首先,对于一种棋盘类型的状态压缩转移。一般就是有两种——
逐行/列
逐格。
然后,我们先看看插头的定义是什么?
对于一个格子。有四个方向。那么每个方向如果有一条线穿过一条边。
那么,就称这一个方向有一个插头。
对于这到题,我们可以发现,每个格子最多有两个插头。
也就是说,最多有6种情况。
我们再看看轮廓线的定义:
蓝线即为轮廓线。(这张图很重要)
如果采用逐行来作,则对于一行状态x,只需要记录他有无下插头。
这样一来就可以推导x+1行有无上插头。
于是,我们来认真看看上面的这个图。
于是,我们可以列出方程:
设f[i,s]表示第i行的轮廓线,插头的状态为s的方案数。
连通性就像上面一样搞即可。
但是,我们一般用最小表示法来弄。
于是上图的连通性可以表示为:
{0 0 1 1} {2 2 1 1} {1 1 1 1}
那么每次转移,我们可以枚举下一行的每个格子的状态。
由于我们可以从上一行状态看到是否有上插头和上一个格子状态看到是否有左插头。
于是,我们可以每次枚举2种状态即可。
总状态<=2^n
然后我们看看连通性的状态——
由于我们要从当前行来入手,那么我们就可以从当前的状态来推,然后就可以发现,这个可以打一个b/dfs来解决。
每行的时间O(n)。
然而,总复杂度为:O((上一行状态+下一行状态+连通性状态)*列的数量)
相当于:O((2^(n+1)+n)*m)
逐列是差不多的。
无法接受。
那么我们看看逐格是怎么实现的。
先看看轮廓线怎么弄。
也就是上面的方法。比原来的一条线多了那么中间的一条线。(多了个插头状态,图中为插头3)
然后由图中可以发现,轮廓线上面有n的格子是与之直接相连的,也有 n+1个插头是与之直接相连的。
那么我们记录上面的状态就可以很好地推出下面的状态。
于是,我们设dp方程:
f[i,j,s]表示当前到第i行,第j列的轮廓线状态为s的方案数。
s表示这n+1个插头的状态。
直观的想法——
下面的框框就是s的表达。(一般用最小表示法)
然后我们仔细观察第二幅图转移到第三幅图的转移方法。
当f[i,j-1,s1]转移到f[i,j,s2]时,我们发现:
上面的格子只有第(i-1,j)被改成了(i,j)
轮廓线也只有两条变化。
所以说,我们可以根据这几个小小的变化来更新图的连通性。
有三个情况:
1、新建一个连通块
2、合并一个连通块
3、什么都不动。
我们就来看看:
(1)新建一个连通块。
我们发现,这种情况的发生当且仅当——
(i-1,j)没有下插头而且(i,j-1)没有右插头。
然后(i,j)新建两个与其他格子插头不相连的插头。
这种情况下需要将这两个插头连通块标号标记成一个未标记过的正数,
重新O(n)扫描保证新的状态满足最小表示。
(2)合并一个连通块
这种情况的发生当且仅当——
(i-1,j)有下插头且(i,j-1)有右插头。
如果这两个插头不连通那么就相当于要合并。
否则就表示出现了一个回路。
继续O(n)扫描维护最小表示即可。
(3)什么都不干
表面上说什么都不干,实际上也是要维护S的。
上面的情况出现当且仅当——
(i-1,j)有下插头或(i,j-1)有右插头。
但是(i,j)可以由下插头或右插头,连通块编号相同,不会影响到其他插头编号。
这个可以O(1)直接维护。
所以说,总的时间复杂度应该是:
O(n^2*m)
差不多吧。
时间复杂度很优秀了。但是空间可能就没有那么地优秀。