Luogu P4767 [IOI2000] 邮局 加强版题解(决策单调DP,四边形不等式优化)
题目描述
坐标轴上有n个村庄,你需要在一些村庄中设立邮局,使每个村庄与其最近的邮局之间的距离总和最小。已知村庄位置与邮局数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。
村庄数量为 V ( n ) V(n) V(n),邮局数量为 P ( m ) P(m) P(m),村庄坐标记录在数组
样例输入 #1
10 5
1 2 3 6 7 9 11 22 44 50
样例输出 #1
9
提示
对于 40 % 40\% 40% 的数据, V ≤ 300 V \leq 300 V≤300。
对于 100 % 100\% 100% 的数据, 1 ≤ P ≤ 300 1 \leq P \leq 300 1≤P≤300, P ≤ V ≤ 3000 P \leq V \leq 3000 P≤V≤3000, 1 ≤ 1 \leq 1≤ 村庄位置 ≤ 10000 \leq 10000 ≤10000 。
思路
我们首先考虑 40 40% 40数据的暴力写法,首先先将村庄坐标从小到大排序,设 f i , j f_{i,j} fi,j代表前 i i i个村庄中设立 j j j个邮局所得到的答案。我们可以先预处理出一个 w w w(也就是代码中的 d i s dis dis)数组。 w i , j w_{i,j} wi,j代表村庄 i i i到 j j j设立只一所邮局的消费。得
w i , j = w i , j − 1 + a j − a ( i + j ) / 2 w_{i,j}=w_{i,j-1}+a_j-a_{(i+j)/2} wi,j=wi,j−1+aj−a(i+j)/2
预处理出 w w w数组后,我们就可以愉快的开始DP惹~
f i , j = m i n ( f i , j , f k , j − 1 + w k + 1 , i ) f_{i,j}=min(f_{i,j},f_{k,j-1}+w_{k+1,i}) fi,j=min(fi,j,fk,j−1+wk+1,i)
最后输出
f
n
,
m
f_{n,m}
fn,m
时间复杂度
O
(
n
m
2
)
O(nm^2)
O(nm2)
代码如下
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3005;
int n,m,a[N*20],f[N][N],w[N][N];
signed main(){
memset(f,0x3f,sizeof(f));
scanf("%lld%lld",&n,&m);
for (int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
w[i][j]=w[i][j-1]+a[j]-a[(i+j)/2];
f[0][0]=0;
for (int j=1;j<=m;j++){
for (int i=1;i<=n;i++){
for (int k=0;k<i;k++){
f[i][j]=min(f[i][j],f[k][j-1]+w[k+1][i]);
}
}
}
printf("%lld",f[n][m]);
return 0;
}
然后就可以……
成功拿下60分
完结撒花
正解
基于上述暴力,我们可以很轻易的想到四边形不等式
w
i
,
j
=
w
i
,
j
−
1
+
a
j
−
a
(
i
+
j
)
/
2
w_{i,j}=w_{i,j-1}+a_j-a_{(i+j)/2}
wi,j=wi,j−1+aj−a(i+j)/2
根据四边形不等式在
a
≤
b
≤
c
≤
d
a \leq b \leq c \leq d
a≤b≤c≤d的情况下
w
a
,
b
+
w
c
,
d
≤
w
a
,
d
+
w
b
,
c
w_{a,b}+w_{c,d} \leq w_{a,d}+w_{b,c}
wa,b+wc,d≤wa,d+wb,c
很明显,我们的DP满足四边形不等式
证明的话后续有时间会更新的。
决策单调性嘛……虽然知道怎么用,但证明我暂时也没搞太明白
回归正题,我们在转移中记录转移的位置到pos,从而降低决策点枚举的数量。
根据决策单调性,我们可以知道
p
o
s
i
,
j
pos_{i,j}
posi,j在
p
o
s
i
,
j
−
1
pos_{i,j-1}
posi,j−1与
p
o
s
i
+
1
,
j
pos_{i+1,j}
posi+1,j
最后输出与暴力的DP一样
最终AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=3005;
int n,m,a[N],pos[N][N],dis[N][N];
long long f[N][N];
signed main(){
memset(f,0x3f,sizeof(f));
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
dis[i][j]=dis[i][j-1]+a[j]-a[(i+j)/2];
f[0][0]=0;
for (int j=1;j<=m;j++){
pos[n+1][j]=n;
for (int i=n;i>=1;i--){
for (int k=pos[i][j-1];k<=pos[i+1][j];k++){
int v=f[k][j-1]+dis[k+1][i];
if (v<f[i][j]){
f[i][j]=v;
pos[i][j]=k;
}
}
}
}
printf("%lld",f[n][m]);
return 0;
}
真正的完结撒花~~
有不足请各位大佬指正小蒟蒻,小蒟蒻没写过几篇题解
后记
另外在写代码中遇到了一个抽象的问题,再写一开始的暴力DP时,若是将我的 d i s dis dis数组的名字换成 w w w,就会多T掉一个点,所以代码中的 w w w换成了 d i s dis dis,若是有大佬知道原因可以在评论区告诉蒟蒻,
感谢!