http://poj.org/problem?id=3709
/*
O(n^2)的DP方程:f[i]=Min{f[j]+sum[i]-sum[j]-a[j+1]*(i-j)}。
假设决策j1<j2并且j2优于(或者不差于)j1,那么
f[j1]+sum[i]-sum[j1]+a[j1+1]*(i-j1) >= f[j2]+sum[i]-sum[j2]-a[j2+1]*(i-j2)
[(f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]*j2)] >= i*(a[j1+1]-a[j2+1])。
a[j2+1]>=a[j1+1], 所以a[j1+1]-a[j2+1] <= 0。
可以写成:[(f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]*j2)] / (a[j1+1]-a[j2+1]) <= i。
对于a[j1+1]==a[j2+1]的情况,不能用除法了,只能用乘法那个表达式,
所以,如果对于决策j1,j2满足上述表达式,则j2 优于 j1。
首先刚开始队首元素为0
用dy(i,j)表示[(f[i]-sum[i]+a[i+1]*i) - (f[j]-sum[j]+a[j+1]*j)]
用dx(i,j)表示(a[i+1]-a[j+1])*i
然后假设队列首尾指针head < tail 并且dy(queue[head],queue[head+1]) >=
i*dx(queue[head],queue[head+1]),那么队首元素直接丢掉就可以了。因为i是递
增的,如果当前queue[head]没有queue[head+1]好,那么今后也不会。
对于队尾的2个原素x, y来说,
如果对于当前i,y比x要差,那么由前面的证明:对于比较大的i,y不一定就比x差,
有可能比x好呢,我们来看看队尾3个元素的情况:x,y,z,如果
dy(x,y)/dx(x,y)>=dy(y,z)/dx(y,z),那么可以直接把y给删了。因为
dy(x,y)/dx(x,y)和dy(y,z)/dx(y,z)是个常数,对于某个i,如果dy(x,y)/dx(x,y)<=i的
话,那么dy(y,z)/dx(y,z)一定也小于等于i,也就是说:如果y优于x,那么z一定优于
y,这个时候留着y就没用了。。。。直接删了。。。
过程就是这些,另外:因为有个限制k,所以决策点需要延迟加
入。
*/
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXX = 500010; int n, k, queue[MAXX]; __int64 sum[MAXX], f[MAXX], a[MAXX]; __int64 dy(int j1, int j2) { return (f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]*j2); } __int64 dx(int j1, int j2) { return (a[j1+1] - a[j2+1]); } void dp() { int i, j, head, tail, x, y, z; head = tail = 0; queue[0] = 0; for(i = 1; i <= n; i++) { while(head<tail && dy(queue[head], queue[head+1])>=i*dx(queue[head], queue[head+1])) head++; j = queue[head]; f[i] = f[j] + sum[i] - sum[j] - a[j+1]*(i-j); if(i >= 2*k-1) //实际上是i-k+1>=k { z = i-k+1; while(head < tail) { x = queue[tail-1]; y = queue[tail]; if(dy(x,y)*dx(y,z) >= dy(y,z)*dx(x,y)) tail--; else break; } queue[++tail] = z; } } } int main() { int t, i; scanf("%d", &t); while(t--) { scanf("%d%d", &n, &k); sum[0] = 0; for(i = 1; i <= n; i++) { scanf("%I64d", a+i); sum[i] = sum[i-1] + a[i]; } dp(); printf("%I64d\n", f[n]); } return 0; }
ok!