题目大意: 数轴上有 n n n 个点,给出他们的坐标 s i s_i si,现在要选一些点建邮局,每个点的代价是离他最近的邮局的距离,找到一种方案使所有点总代价最小。
题解
前置芝士—— w q s wqs wqs 二分,建议先稍微看看,不然下面可能有一丢丢看不懂qwq
可以发现,这题符合 w q s wqs wqs 二分的两个使用条件:
- 设 g ( i ) g(i) g(i) 表示放 i i i 个邮局的最小代价,可以发现,有 g ( i − 1 ) − g ( i ) > g ( i ) − g ( i + 1 ) g(i-1)-g(i)>g(i)-g(i+1) g(i−1)−g(i)>g(i)−g(i+1),也就是说,随着邮局数量增加,虽然代价在减少,但是减少的量一次比一次少,这个感性理解一下还是很显然的。
- 假如没有邮局的数量限制,那么很容易就能求出最小总代价。(这句话最好结合后面的部分一起理解)
根据第一个条件,假如将函数
g
g
g 画出来,是一个下凸包的样子,就像这样:
既然是凸包了,那么我们就可以二分斜率
k
k
k,然后找到最小的截距
F
(
x
)
F(x)
F(x) 即可。
此时明确一下 F ( x ) F(x) F(x) 的定义:表示放一个邮局会有 k k k 的额外代价的前提下,放 x x x 个邮局的最小代价。
发现 F ( x ) F(x) F(x) 并不太好求,考虑求另一个东西: f ( x ) f(x) f(x) 表示前 x x x 个村庄放任意个邮局的最小代价,然后在转移的时候记录一个 s ( x ) s(x) s(x),表示前 x x x 个村庄放 s ( x ) s(x) s(x) 个邮局最优,当 f ( x ) f(x) f(x) 从 f ( y ) f(y) f(y) 转移过来的时候, s ( x ) s(x) s(x) 就变成 s ( y ) + 1 s(y)+1 s(y)+1。
最后, ( s ( n ) , f ( n ) ) (s(n),f(n)) (s(n),f(n)) 就是我们要求的 ( x , F ( x ) ) (x,F(x)) (x,F(x))。
此时没有了数量的限制,只是要让
f
(
n
)
f(n)
f(n) 最小,那么很容易得到方程:
f
(
i
)
=
min
j
=
0
i
−
1
{
f
(
j
)
+
w
(
j
+
1
,
i
)
+
k
}
f(i)=\min_{j=0}^{i-1}\{f(j)+w(j+1,i)+k\}
f(i)=j=0mini−1{f(j)+w(j+1,i)+k}
其中, w ( j + 1 , i ) w(j+1,i) w(j+1,i) 表示在 j + 1 j+1 j+1 到 i i i 之间放一个邮局的最小代价,具体求法不难,可以参照这里。
这个方程看起来是 O ( n 2 ) O(n^2) O(n2) 的,于是我们需要进行优化。
先做一个约定,假如 f ( j ) f(j) f(j) 从 f ( i ) ( i < j ) f(i)~(i<j) f(i) (i<j) 那里转移过来最优,那么我们称 i i i 统治 j j j。优化用到一个结论:对于任意 i i i,他统治的一定是一个连续区间,即不存在对于 a < b < c a<b<c a<b<c, i i i 能统治 a a a 和 c c c 而不能统治 b b b。
当然,一个点可以被多个点统治。
结论证明:
用反证法,假设存在 i , j , a , b , c i,j,a,b,c i,j,a,b,c,满足 i ≠ j , a < b < c i\ne j,a<b<c i=j,a<b<c 且 i i i 统治 a , c a,c a,c, j j j 统治 b b b,并且 i i i 无法统治 b b b。
那么可以知道,
a
,
c
a,c
a,c 从
i
i
i 转移过来最优,从
j
j
j 转移过来不一定最优,所以得到不等式(左右的
k
k
k 消掉了):
f
(
i
)
+
w
(
i
+
1
,
a
)
≤
f
(
j
)
+
w
(
j
+
1
,
a
)
f
(
i
)
−
f
(
j
)
≤
w
(
j
+
1
,
a
)
−
w
(
i
+
1
,
a
)
f(i)+w(i+1,a)\leq f(j)+w(j+1,a)\\ f(i)-f(j)\leq w(j+1,a)-w(i+1,a)
f(i)+w(i+1,a)≤f(j)+w(j+1,a)f(i)−f(j)≤w(j+1,a)−w(i+1,a)
f ( i ) + w ( i + 1 , c ) ≤ f ( j ) + w ( j + 1 , c ) f ( i ) − f ( j ) ≤ w ( j + 1 , c ) − w ( i + 1 , c ) f(i)+w(i+1,c)\leq f(j)+w(j+1,c)\\ f(i)-f(j)\leq w(j+1,c)-w(i+1,c) f(i)+w(i+1,c)≤f(j)+w(j+1,c)f(i)−f(j)≤w(j+1,c)−w(i+1,c)
因为
i
i
i 无法统治
b
b
b,所以对于
b
b
b 而言从
i
i
i 转移过来一定不最优:
f
(
i
)
+
w
(
i
+
1
,
b
)
>
f
(
j
)
+
w
(
j
+
1
,
b
)
f
(
i
)
−
f
(
j
)
>
w
(
j
+
1
,
b
)
−
w
(
i
+
1
,
b
)
f(i)+w(i+1,b)>f(j)+w(j+1,b)\\ f(i)-f(j)>w(j+1,b)-w(i+1,b)
f(i)+w(i+1,b)>f(j)+w(j+1,b)f(i)−f(j)>w(j+1,b)−w(i+1,b)
于是可以得到:
{
w
(
j
+
1
,
a
)
−
w
(
i
+
1
,
a
)
>
w
(
j
+
1
,
b
)
−
w
(
i
+
1
,
b
)
w
(
j
+
1
,
c
)
−
w
(
i
+
1
,
c
)
>
w
(
j
+
1
,
b
)
−
w
(
i
+
1
,
b
)
\begin{cases} w(j+1,a)-w(i+1,a)>w(j+1,b)-w(i+1,b)\cr w(j+1,c)-w(i+1,c)>w(j+1,b)-w(i+1,b) \end{cases}
{w(j+1,a)−w(i+1,a)>w(j+1,b)−w(i+1,b)w(j+1,c)−w(i+1,c)>w(j+1,b)−w(i+1,b)
⇒ { w ( j + 1 , a ) + w ( i + 1 , b ) > w ( j + 1 , b ) + w ( i + 1 , a ) ( 1 ) w ( j + 1 , c ) + w ( i + 1 , b ) > w ( j + 1 , b ) + w ( i + 1 , c ) ( 2 ) \Rightarrow \begin{cases} w(j+1,a)+w(i+1,b)>w(j+1,b)+w(i+1,a)~~(1)\cr w(j+1,c)+w(i+1,b)>w(j+1,b)+w(i+1,c)~~(2) \end{cases} ⇒{w(j+1,a)+w(i+1,b)>w(j+1,b)+w(i+1,a) (1)w(j+1,c)+w(i+1,b)>w(j+1,b)+w(i+1,c) (2)
因为
w
w
w 满足四边形不等式(证明也在这里),所以当
i
+
1
<
j
+
1
i+1<j+1
i+1<j+1 时,式子
(
1
)
(1)
(1) 不成立,当
i
+
1
>
j
+
1
i+1>j+1
i+1>j+1 时,式子
(
2
)
(2)
(2) 不成立,所以这个不等式组在任何情况下都不成立,即假设不成立,不存在i,j,a,b,c,满足i不等于j,a<b<c 且i统治a,c,j统治b,并且i无法统治b
。
证毕。
接下来就可以用这个结论优化 d p dp dp,维护一个队列,里面从左到右依次记录着每个区间被谁统治(可能一个点会被多个点统治,但是没必要把这些点都记下来)。
枚举到点 i i i 时,看看谁统治着点 i i i,直接从它转移过来。
然后再看他能统治后面的哪一段区间,从最后一段区间(即包含 n n n 的区间)看起,一直往前枚举,能拿一段是一段,枚举到有一段不能全部拿走时,就在这一段进行二分,能拿多少拿多少。
这个过程的原理就是上面的结论,具体一点就是:由于 i i i 的统治区间连续,所以能拿多少个点就拿多少,拿不了就停止了,不再继续往前枚举。而对于每个 i i i,一定从最后一段区间开始枚举,不能从中间开始,不然可能导致出现类似 a a a 统治 d d d, b b b 统治 c c c 的情况(其中 a < b < c < d a<b<c<d a<b<c<d),根据四边形不等式,我们知道交叉小于包含,所以这种情况一定是不优秀的。
最后就是代码了:
#include <cstdio>
#include <cstring>
#define maxn 3010
#define mid (l+r>>1)
int n,m,a[maxn],sum[maxn],ans;
int l=0,r=1000000;
int w(int l,int r){ return sum[r]-sum[mid]-(r-mid)*a[mid]+(mid-l)*a[mid]-(sum[mid-1]-sum[l-1]); }
struct node{int pos,l,r;};//pos统治区间[l,r]
node q[maxn];
int f[maxn],s[maxn],t;
int calc(int from,int now,int cost){return from>now?999999999:f[from]+w(from+1,now)+cost;}
int solve(int cost)
{
q[t=1]=(node){0,1,n};
for(int i=1;i<=n;i++)
{
int l=1,r=t,pos;
while(l<=r)q[mid].l<=i?(l=(pos=mid)+1):(r=mid-1);
//找到统治i的点,找到i在哪个区间内
f[i]=calc(q[pos].pos,i,cost);s[i]=s[q[pos].pos]+1;
pos=-1;//从最后一个区间看起,看看i能统治多少个区间
while(t>0&&calc(i,q[t].l,cost)<=calc(q[t].pos,q[t].l,cost))pos=q[t--].l;
//最后那个不能全部统治的区间二分一下,能统治多少是多少
if(t>0&&calc(i,q[t].r,cost)<=calc(q[t].pos,q[t].r,cost))
{
l=q[t].l,r=q[t].r;
while(l<=r)calc(i,mid,cost)<=calc(q[t].pos,mid,cost)?(r=(pos=mid)-1):(l=mid+1);
q[t].r=pos-1;
}
if(pos!=-1)q[++t]=(node){i,pos,n};//加上i统治的新区间
}
return s[n];
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
while(l<=r)
{
if(solve(mid)>=m)ans=f[n]-m*mid,l=mid+1;
else r=mid-1;
}
printf("%d",ans);
}