题意很简单,给你若干个数字,你需要减去一些数字,使得在数列中的每个数字出现的次数不少于k次。
一开始我们都会想到是用DP,于是很快我们就可以得出状态为搞定前面i个数所需要花费的最小代价用f[i]表示。
接下来我们可以得到状态转移方程为f[i]=min(f[j]+sum[i]-sum[j]-(i-j)*a[j+1]);j的枚举范围为k到i-k。这是很容易理解的。
但是再看题目我们就会发现这个算法的时间复杂度为O(N^2),题目中N多大500000,根本无法承受。所以需要优化的说。
接下来要说的就是优化了,这个叫做斜率优化的说。我们假设当前要求f[i],那么对于前面的任意两个j1和j2(j1<j2),如果j2优于j1,那么一定满足f[j1]-f[j2]-sum[j1]+sum[j2]+j1*a[j1+1]-j2*a[j2+1]>i*(a[j1+1]-a[j2+1]);这样我们显然可以直接不考虑j1,因为a[j1+1]-a[j2+1]<=0,等式左边是常数,所以随着i的增加,这个等式一定是恒成立的。这是队首的优化。
同时对于每一次入队之前,对于队尾都有一个优化。那就是我们对于将要入队的那个元素和队尾的两个元素x,y(总共三个元素)顺序两两比较,有一种情况就是对于任意的i,只要y比x优,那么z就比y有,用公式表达就是dy(x,y)/dx(x,y)>=dy(y,z)/dx(y,z)。由于除法会有0的情况,所以在程序中间进行判断的时候要写成乘法的形式。(有负数,注意变号的问题)。那么在这种情况下面,y显然是无效的,我们可以直接从队尾把y删除。
你可能会问,队尾的删除操作是不是一定是必须的呢?一开始我也有这个问题,我认为这是不需要的,因为每次比较的都是两个元素,直到我交上去Wa出了翔。没有队尾的删除操作为什么是错的呢?其实是这样的,因为每次在队首的比较我们都是比较的两个元素来决定队首的元素是否有效,但是如果没有队尾的操作,那么队首的操作也是不正确的。因为x(设为队首第一个)在此时优于y(队首第二个),但是这并不能说明x优于队列中的每一个元素,这就是错误的原因。
下面是我的代码,如果你觉得和网上的神牛的代码长得很像的话我没有意见,因为我一开始是不会做,看着别人的代码才写出来的。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxn 500500 5 typedef long long ll; 6 using namespace std; 7 8 ll f[maxn],n,m,k,t,a[maxn],sum[maxn],Q[maxn],head,tail; 9 10 ll dy(ll j1,ll j2) 11 { 12 return f[j1]-f[j2]-sum[j1]+sum[j2]+j1*a[j1+1]-j2*a[j2+1]; 13 } 14 ll dx(ll j1,ll j2) 15 { 16 return a[j1+1]-a[j2+1]; 17 } 18 19 int main() 20 { 21 scanf("%I64d",&t); 22 while (t--) 23 { 24 scanf("%I64d%I64d",&n,&k); 25 sum[0]=0; 26 for (int i=1; i<=n; i++) scanf("%I64d",&a[i]),sum[i]=sum[i-1]+a[i]; 27 head=tail=0,Q[0]=0; 28 for (ll i=1; i<=n; i++) 29 { 30 while (head<tail && dy(Q[head],Q[head+1])>i*dx(Q[head],Q[head+1])) head++; 31 ll j=Q[head],z=i-k+1; 32 f[i]=f[j]+sum[i]-sum[j]-(i-j)*a[j+1]; 33 if (z>=k) 34 { 35 while (head<tail) 36 { 37 ll x=Q[tail-1],y=Q[tail]; 38 if (dy(x,y)*dx(y,z)>=dy(y,z)*dx(x,y)) tail--; 39 else break; 40 } 41 Q[++tail]=z; 42 } 43 } 44 printf("%I64d ",f[n]); 45 } 46 return 0; 47 }
下面这个是我自己写的,进行了一些常数的优化,但是时间依然是1s+,知道如何优化才能把时间优化到200ms左右,真是费解。有神牛如果知道了,求告诉我一声。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxn 500500 5 typedef long long ll; 6 using namespace std; 7 8 ll f[maxn],n,m,k,t,a[maxn],sum[maxn],Q[maxn],head,tail,pos[maxn],sy[maxn],yy[maxn],cx,cy; 9 10 ll dy(ll j1,ll j2) 11 { 12 return sy[j2]-sy[j1]; 13 } 14 ll dx(ll j1,ll j2) 15 { 16 return a[j1+1]-a[j2+1]; 17 } 18 19 int main() 20 { 21 scanf("%I64d",&t); 22 while (t--) 23 { 24 scanf("%I64d%I64d",&n,&k); 25 sum[0]=0; 26 for (int i=1; i<=n; i++) scanf("%I64d",&a[i]),sum[i]=sum[i-1]+a[i]; 27 head=tail=0,Q[0]=0,pos[0]=n+1; 28 for (ll i=1; i<=n; i++) 29 { 30 while (head<tail && i>pos[head]) head++; 31 ll j=Q[head],z=i-k+1; 32 f[i]=f[j]+sum[i]-sum[j]-(i-j)*a[j+1]; 33 if (z>=k) 34 { 35 while (head<tail) 36 { 37 if (dy(Q[tail],z)>=(pos[tail-1]+1)*dx(Q[tail],z)) tail--; 38 else break; 39 } 40 Q[++tail]=z; 41 cx=dx(Q[tail-1],Q[tail]),cy=dy(Q[tail-1],Q[tail]); 42 if (cx==0) 43 { 44 if (cy>=0) pos[tail-1]=0; 45 else pos[tail-1]=n+1; 46 } 47 else pos[tail-1]=cy/cx;//直接保存当前队列中的数的最远的有效位置。不过好像在删除的时候要更新,不过我的没有更新了,不会影响答案。 48 } 49 yy[i]=f[i-1]-f[i]-sum[i-1]+sum[i]+(i-1)*a[i]-i*a[i+1]; 50 sy[i]=sy[i-1]+yy[i];//dy可以叠加,所以只要求和然后相减就可以了。 51 } 52 printf("%I64d ",f[n]); 53 } 54 return 0; 55 }