【GDOI2014模拟】服务器题解

探讨了在多服务器环境下,如何通过直接或间接复制文件至各服务器,以最小化总成本的算法设计。采用动态规划结合斜率优化技术,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

我们需要将一个文件复制到n个服务器上,这些服务器的编号为S1, S2, …, Sn。
首先,我们可以选择一些服务器,直接把文件复制到它们中;将文件复制到服务器Si上,需要花费ci > 0的置放费用。对于没有直接被复制文件的服务器Si来说,它依次向后检查Si+1, Si+2, …直到找到一台服务器Sj:Sj中的文件是通过直接复制得到的,于是Si从Sj处间接复制得到该文件,这种复制方式的读取费用是j – i(注意j>i)。另外,Sn中的文件必须是通过直接复制得到的,因为它不可能间接的通过别的服务器进行复制。我们设计一种复制方案,即对每一台服务器确定它是通过直接还是间接的方式进行复制(Sn只能通过直接方式),最终使每一台服务器都得到文件,且总花费最小。

Solution

首先,我相信各位巨佬都可以一眼秒出时间复杂度 O ( n 2 ) O(n^2) O(n2)的dp。
不过我这个蒟蒻还是要多嘴一下,dp转移式就是:
f [ i ] = m i n ( f [ i ] , f [ j ] + ( j − i ) ∗ ( j − i − 1 ) 2 + c [ i ] ) f[i]=min(f[i],f[j]+\frac{(j-i)*(j-i-1)}{2}+c[i]) f[i]=min(f[i],f[j]+2(ji)(ji1)+c[i])
是不是很简单呢?
只可惜,这么美妙的一个方法,是会TLE的。
所以我们考虑优化。
要是有些斜率优化底子的人应该会想到斜率优化。
首先,如果从 j j j这个位置转移到 i i i这个位置要比从 k k k这个位置转移要优,那么一定会满足下面的这个式子:
f [ j ] + ( j − i ) ∗ ( j − i − 1 ) 2 + c [ i ] < f [ k ] + ( k − i ) ∗ ( k − i − 1 ) 2 + c [ i ] f[j]+\frac{(j-i)*(j-i-1)}{2}+c[i]<f[k]+\frac{(k-i)*(k-i-1)}{2}+c[i] f[j]+2(ji)(ji1)+c[i]<f[k]+2(ki)(ki1)+c[i]
化简一下 ( j − i ) ∗ ( j − i − 1 ) 2 \frac{(j-i)*(j-i-1)}{2} 2(ji)(ji1) ( k − i ) ∗ ( k − i − 1 ) 2 \frac{(k-i)*(k-i-1)}{2} 2(ki)(ki1)可得:
f [ j ] + j 2 2 + j 2 − i j + i 2 2 − i 2 < f [ k ] + k 2 2 + k 2 − i k + i 2 2 − i 2 f[j]+\frac{j^2}{2}+\frac{j}{2}-ij+\frac{i^2}{2}-\frac{i}{2}<f[k]+\frac{k^2}{2}+\frac{k}{2}-ik+\frac{i^2}{2}-\frac{i}{2} f[j]+2j2+2jij+2i22i<f[k]+2k2+2kik+2i22i
把两边只跟 i i i有关的全不消掉,得:
f [ j ] + j 2 2 + j 2 − i j < f [ k ] + k 2 2 + k 2 − i k f[j]+\frac{j^2}{2}+\frac{j}{2}-ij<f[k]+\frac{k^2}{2}+\frac{k}{2}-ik f[j]+2j2+2jij<f[k]+2k2+2kik
把跟 i i i有关的移到一边,把只跟 j j j有关或只跟k有关的移到一边,得:
f [ j ] + j 2 2 + j 2 − f [ k ] − k 2 2 − k 2 < i j − i k f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}<ij-ik f[j]+2j2+2jf[k]2k22k<ijik
把右边的 i i i提出来,得:
f [ j ] + j 2 2 + j 2 − f [ k ] − k 2 2 − k 2 < ( j − k ) i f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}<(j-k)i f[j]+2j2+2jf[k]2k22k<(jk)i
( j − k ) (j-k) (jk)除过去另一边,得:
f [ j ] + j 2 2 + j 2 − f [ k ] − k 2 2 − k 2 ( j − k ) < i \frac{f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}}{(j-k)}<i (jk)f[j]+2j2+2jf[k]2k22k<i
这样,是不是就很眼熟了呢?这就是我们所熟悉的斜率,然后搞个单调队列,下凸壳,便可以直接维护了。

Code

#include<cstdio>
#include<algorithm>
#define ll long long
#define ldb long double
using namespace std;
const int N=1e6+5;
ll n,head,tail;
ll a[N],f[N],ans,q[N];
ldb slope(ll x,ll y) {return (f[x]-f[y]+(x*x-y*y-x+y)*1.0/2)*1.0/(x-y)*1.0;}//斜率 
int main() {
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);
	q[0]=n;f[n]=a[n];
	ans=a[n]+(n*(n-1)/2);
	for(ll i=n-1;i;i--) {
		while(head<tail&&slope(q[head],q[head+1])>=i)head++;//如果大于等于i就是q[head]没有q[head+1]优 
		ll j=q[head];
		f[i]=f[j]+(j-i)*(j-i-1)/2+a[i];
		ans=min(ans,f[i]+(i*(i-1)/2));
		while(head<tail&&slope(q[tail],i)>=slope(q[tail-1],q[tail]))tail--;//下凸壳 
		q[++tail]=i;
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值