【GDOI2014模拟】服务器 (斜率单调队列优化Dp)

本文介绍了一种使用单调队列优化动态规划算法的方法,用于解决文件在多个服务器间高效复制的问题,确保了时间和空间复杂度的有效控制。

Description

我们需要将一个文件复制到n个服务器上,这些服务器的编号为S1, S2, …, Sn。

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

Input

输入文件的第一行有一个整数n,表示服务器的数目。输入文件的第二行有n个整数,顺数第i个表示ci:在Si上直接复制文件的费用。

Output

输出文件中只包含一个整数,即最少需要花费的费用。

Sample Input

10

2 3 1 5 4 5 6 3 1 2

Sample Output

18

Data Constraint

60%的数据中,1 <= n <= 1 000

100%的数据中,1 <= n <= 1 000 000

80%的数据中, 1 <= ci <= 50

100%的数据中,1 <= ci <= 1 000 000 000

最终结果可能较大,请注意选择适当的数据类型进行计算。

The solution

我就直接入题吧,这道题很容易想到Dp方程

f[i]=min(f[j]+a[i]+(ji)(ji1)2,f[i),j>i
这样的方程明显是O(n2) 肯定TLE,期望的得分60分

60分的CODE如下:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define INF 214783647
#define N 1000005
using namespace std;
long long f[N],c[N];
long long n,m,ans=0;
long long work(int x,int y)
{
    return (long long)y*(y-x)-(long long)(y-x)*(x+y-1)/2;
}
int main()
{
    scanf("%lld",&n);
    fo(i,1,n) scanf("%lld",&c[i]);
    ans=(f[n]=c[n])+work(1,n);
    fd(i,n-1,1)
    {
        f[i]=INF;
        fo(j,i+1,n) f[i]=min(f[i],f[j]+work(i+1,j));
        f[i]+=c[i];
        ans=min(ans,f[i]+work(1,i));
    }
    printf("%lld",ans);
}

怎么优化这个Dp呢,我们可以考虑用斜率单调队列优化Dp
(在这里感谢LYD729大神。。。。%%%)
我们可以
对于当前的i(i是从后往前枚举的),设有两个决策j,k,j优于k,那么需要满足的条件是

f[j]+(ji)(ji1)2+a[i]<f[k]+(ki)(ki1)2+a[i]

将上述方程移项可得:

f[j]f[k]+j2k2j+k2jk<i

这种模式就可以清晰的看到此方程的单调性,就可以考虑单调队列了。

设Work(j,k)表示不等式左边。

我们就可以维护一个单调队列

i>Work(jl,jl+1)>Work(jl+1,jl+2)>Work(jl+2,jl+3)>......>Work(jr1,jr))

对头的jl即为最优解

若有

g(jr1,jr)<g(jr,i)
则说明jr永远不可能取到最优了,证明如下:

由于本人蒟蒻,目前还无法理解证明,以下证明源于LYD729,再次%%%%

采用反证法,假设jr能够取到最优,则
jrjr1g(jr1,jr)>i
jrig(jr,i)<i
综合上面两个不等式,得
g(jr,i)<i<g(jr1,jr)
惊奇地发现不等式出现了矛盾!
得证!
所以要把jr踢掉,一直做下去直到不满足上述条件,然后让i入队。

单调队列的题目还得多练啊,自己还是有点不熟悉,
下次再遇到这种题可能就要滚粗了。。。→_→。。。
还得多练多努力啊!!!

ACCEPTED
CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define INF 214783647
#define N 1000005
using namespace std;
long long F[N],c[N],d[N];
long long n,m;

double Work(long long x,long long y)
{
    return (F[x]-F[y]+(double)(x*x-y*y-x+y)/2)/(double)(x-y);
}
int main()
{
    freopen("server.in","r",stdin);
    scanf("%lld",&n);
    fo(i,1,n) scanf("%lld",&c[i]);
    F[n]=c[n];
    int l=1,r=1;
    d[1]=n;
    fd(i,n-1,0)
    {
        while (l<r && i<Work(d[l],d[l+1])) l++;
        F[i]=F[d[l]]+(d[l]-i)*(d[l]-i-1)/2+c[i];
        while (l<r && Work(d[r-1],d[r])<Work(d[r],i)) r--;
        d[++r]=i;    
    }
    printf("%lld",F[0]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值