斜率优化dp
题意:
将题目转化下:将一个升序的,有N个元素的序列,分组。要求每组的元素不少于K个,计算出组内各元素与最小元素的之差的和,将每组的这个值加起来,其和要最小。
很容易可以得出一个结论:连续取比离散取得到的结果要好(很容易证,所以不证)。
由以上可得DP方程:
dp[i]=MIN(dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]); j<i-k+1
f[i] = min{f[j] +j *x[j+1] -sum[j] -i *x[j+1]} +sum[i] (转化后的)
这道题需要注意的就是有k的限制,因此需要延迟入队~
就是说走完 i 这个点的时候,把下一个点即(i+1)可能用到的状态加入队列,就是i-k+1
但具体的入队时间呢,
若延迟K-1个回合加入,有可能使前一组的个数少于K个。
从开始算起,如果延迟一个回合加入了,那么利用f[1到k]跟新了f[i],而f[1到k]并不是满足数量大于等于k~~
若延迟2*k-1个回合加入,则不会出现这情况。但此时加入的数应是i-k+1(假设是第I回合),因为第1到k个的f值实在是不能用,而第k之后的是通过f[0]更新的,相当于第一个分组大于等于k个,然后后面的就可以用这个状态开始更新了~~
要更新下一个的时候才加入i-k+1,这样能保证即使队列弹到空也能保证间隔的数目是大于等于k的,而且只有当前面的解没后面的解优时才会弹出,所以此时的队尾值必定是最优的~
具体判断斜率大小关系的时候应该这样,因为要弹队列尾,那么就一定是后加入的要优于队尾的,然后推出一个大小关系,顺延到前面,证明一下合理性就ok了,注意过程中符号的变换~
#include<bits/stdc++.h>
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;
}
#include <iostream>
#include <cstdio>
#include <string.h>
#include <climits>
#include <algorithm>
#include <deque>
using namespace std;
#define ll long long
const int maxn=5e5+10;
ll sum[maxn],f[maxn],a[maxn];
int t ,n,k;
ll dy(int i,int j){
return (f[i]-sum[i]+i*a[i+1])-(f[j]-sum[j]+j*a[j+1]);
}
ll dx(int i,int j){
return (a[i+1]-a[j+1]);
}
int main(){
cin>>t;
while(t--){
deque<int>q;
scanf("%d%d",&n,&k);memset(f,0,sizeof(f));
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
q.push_back(0);
for(int i=1;i<=n;i++){
while(q.size()>=2&&(dy(q[0],q[1])>=i*(dx(q[0],q[1])))){
q.pop_front();
}
int now=q.front();
f[i]=f[now]+sum[i]-sum[now]-a[now+1]*(i-now);
if(i-k+1>=k){
int z=i-k+1;
while(q.size()>=2){
int x=q[q.size()-2];int y=q[q.size()-1];
if(dy(x,y)*dx(y,z)>=dy(y,z)*dx(x,y)) q.pop_back();
else break;
}
q.push_back(i-k+1);
}
}
printf("%lld\n",f[n]);
}
}