考场上能想出这道题挺爽得,还剩4分钟的时候A了,copyhls 的B的代码只改了一点点变量头文件和return 0竟然没被判重,结果是long long+=int*int,后面的会变成负数的问题。
思考过程:
看到数据范围是1e5 要么nlogn要么On,nlogn一般是dp+二分维护/线段树等数据结构维护,On一般是贪心或者dp(单调队列,单调栈优化)
首先想贪心的想法,这题就是要找一种划分方式使得能删去的数字的总和最大,那么我们观察第二个样例想到如果把一大堆大的数字放在一起的话,就可以删掉一些比较大的数字,因为他删除的是这个划分区间中最小的几个数字,所以最好是把较大的数字放在一个区间里。
然而贪心好像还是不知道怎么去划分,接着我们想,既然是删掉k/c个,那么k<c是不删除的无意义,k=c删除1个,c<=k<2c还是删除1个,那么相当于第c+1个到第k个放在上一区间是无意义的。证明:假如前c个的最小值是mini ,如果a[c+1]<mini,那么删掉的就是a[c+1],不值得,如果a[c+1]>=mini,那么删除的还是mini,吧c+1个划分到上个区间也是无意义的。
但如果长度为2c的话就能删除2个,但一定不如化成2个c长度的区间优秀。假如前c个的最小值是mini1,次小值是mini2,后c个的最小值是mini3,假如mini2<mini3,那么删除mini1+mini3>mini1+mini2,划分成2段更好,如果mini2>=mini3,那么删除的还是mini1+mini3,是等价的。
这样我们就能想到dp了,f[i]为到i位置能减去的最大的和是多少,可以从f[i-1]转移过来,就相当于把i分到一个长度不到c的区间里,无法减去值,也可以从f[i-c]转移过来,说明吧i-c+1到i分成一个长度为c的区间,然后删掉里面的最小值,用单调队列维护最小值即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxl 100010
using namespace std;
int n,c,l,r;
int a[maxl],num[maxl],ind[maxl];
long long f[maxl];
long long sum=0;
int main()
{
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),sum+=a[i];
l=1;r=0;int pre;
for(int i=1;i<=n;i++)
{
while(num[r]>a[i] && r>0)
r--;
ind[++r]=i;num[r]=a[i];
if(r<l)
l=r;
pre=max(0,i-c);
if(ind[l]<=pre)
l++;
if(pre==i-c)
f[i]=max(f[pre]+num[l],f[i-1]);
else
f[i]=f[i-1];
}
printf("%I64d",sum-f[n]);
return 0;
}