题目描述:
建邮局,每个村庄会到最近的邮局去寄信,要求村庄到邮局的总距离最小
分析:
假设要在
[l,r]
[
l
,
r
]
村庄中建一个邮局,要使代价最小,那么村庄一定建在第
l+r2
l
+
r
2
个村庄(中位数的知识)
我们预处理出
w(l,r)
w
(
l
,
r
)
,表示在
[l,r]
[
l
,
r
]
建一座邮局的花费
于是就有递推式:
w(l,r)=w(l,r−1)+dis[r]−dis[l+r2]
w
(
l
,
r
)
=
w
(
l
,
r
−
1
)
+
d
i
s
[
r
]
−
d
i
s
[
l
+
r
2
]
(在递推的时候,中点最多向后移动一个,而
[l,r−1]
[
l
,
r
−
1
]
到达新中点的距离之和不变)
设计方程
f[i][j]
f
[
i
]
[
j
]
,表示在前
i
i
个村庄建立个邮局的最小花费
于是有dp转移方程:
f[i][j]=min(f[k][j−1]+w(k+1,i))
f
[
i
]
[
j
]
=
m
i
n
(
f
[
k
]
[
j
−
1
]
+
w
(
k
+
1
,
i
)
)
感觉好像可以用斜率优化
然而这道题实际上可以用四边形不等式优化
这个式子是满足决策单调性的,四边形不等式优化可以使时间复杂度从 O(n3) O ( n 3 ) 降到 O(n2). O ( n 2 ) .
我们怎么判断式子是否有决策单调性呢
给出相关的定理:
- 对于函数
w[i][j]
w
[
i
]
[
j
]
,若
w[i][j]+w[i+1][j+1]<=w[i][j+1]+w[i+1][j]
w
[
i
]
[
j
]
+
w
[
i
+
1
]
[
j
+
1
]
<=
w
[
i
]
[
j
+
1
]
+
w
[
i
+
1
]
[
j
]
则 w w 满足凸多边形不等式(交叠的小于等于包含) - 对于函数,若 w[a,b]<=w[c,d](c<=a<=b<=d w [ a , b ] <= w [ c , d ] ( c <= a <= b <= d ,那么我们称 w w 关于区间包含状态单调
定理1:w满足凸多边形不等式和区间包含状态单调,那么dp也满足四边形不等式
定理2:最优决策满足 k[i][j−1]<=k[i][j]<=k[i+1][j] k [ i ] [ j − 1 ] <= k [ i ] [ j ] <= k [ i + 1 ] [ j ]
这是四边形不等式优化dp的关键,利用这个定理可以每次缩小 k k 的枚举范围,可以使时间复杂度从降到 O(n2) O ( n 2 )定理3: w w 为凸当且仅当
定理3其实告诉我们验证 w w 是否为凸的方法:
固定一个变量,看成是一个一元函数,进而判断单调性
如,我们可以固定算出 w[i][j+1]−w[i][j] w [ i ] [ j + 1 ] − w [ i ] [ j ] 关于 i i 的表达式,如果关于是递减,则 w w 为凸在实际的应用中我们往往不能用数学方法证明为凸,不过我们可以通过打表找规律的方法来发现dp的决策单调性
tip
注意循环的顺序
还是我们的原则:从稳定状态转移
因为我们在转移的时候会用到 s[i−1][j] s [ i − 1 ] [ j ] ,所以第一维需要从 1 1 到循环
还会用到 s[i][j+1] s [ i ] [ j + 1 ] ,所以第二维需要从 n n 到循环
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=305; const int INF=1e9; int f[30][N],w[N][N],s[30][N],dis[N],n,m; void prepare() { for (int i=1;i<=n;i++) { w[i][i]=0; for (int j=i+1;j<=n;j++) { w[i][j]=w[i][j-1]+dis[j]-dis[(i+j)/2]; } } } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&dis[i]); prepare(); for (int i=1;i<=n;i++) f[1][i]=w[1][i],s[1][i]=0; for (int i=2;i<=m;i++) { s[i][n+1]=n; for (int j=n;j>i;j--) { f[i][j]=INF; for (int k=s[i-1][j];k<=s[i][j+1];k++) if (f[i][j]>f[i-1][k]+w[k+1][j]) { f[i][j]=f[i-1][k]+w[k+1][j]; s[i][j]=k; } } } printf("%d",f[m][n]); return 0; }