【问题描述】
你每天会收到到一些欧元,可能正也可能负,银行允许你在某天将手头所有的欧元
兑换成人民币。第 i 天的兑换比率是 1 欧元 : i 元人民币,同时你必须再多付出 T 元人
民币被银行收取。在 N 天你必须兑换所有持有的欧元。
你的任务是找一个方案使第 N 天结束时能得到最多的人民币。
【输入格式】
第一行是整数 N, T;
第二行是 N 个整数 Ci,表示第 i 天开始时得到 Ci 的欧元。
【输出格式】
输出最终得到的最多人民币。
【样例】
euro.in euro.out
7 1
-10 3 -2 4 -6 2 3
17
【样例说明】样例可在第1,5,7天的时候兑换。
【数据规模】
100%的数据:1 ≤ N ≤ 34567,0 ≤ T ≤ 34567,每天收到的欧元在[-1000,1000],结
果保证在long long范围内。
题解
首先考虑最朴素的DP:设 f[ i ] 表示前i天兑换欧元所能得到的最大收益
则f[ i ]=max{f[j]+(s[i]-s[j])*i-T};
恩,看到长成这样的东西,我们很容易想到斜率优化dp
整理一下,f[i]=max{f[j]+s[i]*i-s[j]*i-T}
假设 j 就是我们要找的最优解,f[i]=f[j]+s[i]*i-s[j]*i-T;
f[j]=s[j]*i+f[i]-s[i]*i+T;
把 i 看做斜率 k ,s[j]看成x。。等等,s[j]不是单调的,麻烦了。
怎么办呢,考虑整个序列,根据题(chang)意(shi)可得,每一个兑换的时间点(最后一次除外)都是收到负数的欧元时进行的
可以感性的理解一下,就是不会再还能挣钱的时候就抛了,等亏钱再抛
比如说现在是8 -9(第3天,第4天)如果在第3天换的话是3*8-4*9=-12 第4天换就是(8-9)*4=-4
这样我们就可以考虑在每一个可能抛出的点进行DP
弄一个前缀和s记录从上次值为负到这次值为负之间的数之和
然后对这个东西斜率优化,显然b[i].s是单调递减的,因为每一个元素都是负的
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=40005;
int a[N];
long long s[N],q[N],f[N];
struct node
{
long long x;
int id;
}b[N];
double xielv (int x,int y)
{
return 1.0*(f[x]-f[y])/(s[x]-s[y]);
}
int main()
{
freopen("euro.in","r",stdin);
freopen("euro.out","w",stdout);
int i,n,m=0,t,l=1,r=1;
scanf("%d%d",&n,&t);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]+=a[i-1];
if(a[i]<0)
{
b[++m].x=a[i];
b[m].id=i;
a[i]=0;
}
}
b[++m].x=a[n];
b[m].id=n;
for(i=1;i<=m;i++)
s[i]=s[i-1]+b[i].x;
for(i=1;i<=m;i++)
{
while(l<r&&xielv(q[l],q[l+1])<1.0*b[i].id)
l++;
f[i]=f[q[l]]+(s[i]-s[q[l]])*b[i].id-t;
while (l<r&&xielv(q[r-1],q[r])>xielv(q[r],i))
r--;
q[++r]=i;
}
printf("%lld\n",f[m]);
return 0;
}