IOI 2000 邮局 题解(2)

题目传送门

题目大意: 数轴上有 n n n 个点,给出他们的坐标 s i s_i si,现在要选一些点建邮局,每个点的代价是离他最近的邮局的距离,找到一种方案使所有点总代价最小。

题解

前置芝士—— w q s wqs wqs 二分,建议先稍微看看,不然下面可能有一丢丢看不懂qwq

可以发现,这题符合 w q s wqs wqs 二分的两个使用条件:

  1. 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(i1)g(i)>g(i)g(i+1),也就是说,随着邮局数量增加,虽然代价在减少,但是减少的量一次比一次少,这个感性理解一下还是很显然的。
  2. 假如没有邮局的数量限制,那么很容易就能求出最小总代价。(这句话最好结合后面的部分一起理解)

根据第一个条件,假如将函数 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=0mini1{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=ja<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);
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值