斜率优化动态规划

关键技术:

1.单调队列:

1.1队首出队:重点考虑条件是什么

1.2队尾出队:重点考虑条件是什么

2.斜率:

2.1斜率是什么:

2.2斜率有什么用:用来控制单调队列的出队

3.动态规划:

3.1首先必须会朴素的动态规划

3.2能够写出其递归公式

3.3斜率优化动态规划只能够解决类似于模板题的动态规划

问题:给定n个数a[i],连续的若干数可以分成一组,将这些数分成若干组,每组大小不小于k,每组代价为组内元素和的平方,求最小代价和(求区间的平方和)

 解:

设sum[i]表示a[i]的前缀和,f[i]表示前i个数分组后的最小代价和

f[i]=min(f[j]+(sum[i]-sum[j])^2) (i-j>=k)

注:此动归不能用单调队列优化,因为拆开后会得到sum[i]*sum[j],i和j不能单独分开

4.斜率优化(队列优化):

就是把决策与决策之间表示成一个类似于斜率\frac{y1-y2}{x1-x2}的式子,进一步分析其中的单调性,并用队列维护其有用决策(用斜率作为标尺出队)

队首出队是为了删除过期的队首信息,队尾出队是为了维持单调队列的性质

模板题的递推公式展开之后为:

f[i]=f[j]+s[i]^2-2*s[i]s[j]+s[j]^2

因为s[i],s[j],f[j]是已知的,f[i]未知,因此将f[i]放到截距里面

f[j]+s[j]^2=s[i]*2s[j]+(f[i]-s[i]^2)

令y=f[j]+s[j]^2,k=s[i],x=2s[j],b=f[i]-s[i]^2

上式可转换成y=kx+b

总结:把仅与i相关的项替换成b,把与i,j相关的项替换成kx(x与j相关,k与i相关),把仅与j相关的项替换成y

#include <bits/stdc++.h>
#define N 100000

using namespace std;

int f[N];//动态规划
int q[N] , head , tail;//单调队列
int sum[N];//前缀和
int n , k , a[N];//k表示分组的最小长度
/*
f[i]=f[j]+s[i]^2-2*s[i]s[j]+s[j]^2

因为s[i],s[j],f[j]是已知的,f[i]未知,因此将f[i]放到截距里面

f[j]+s[j]^2=s[i]*2s[j]+(f[i]-s[i]^2)

令y=f[j]+s[j]^2,k=s[i],x=2s[j],b=f[i]-s[i]^2

上式可转换成y=kx+b

总结:把仅与i相关的项替换成b,把与i,j相关的项替换成kx(x与j相关,k与i相关),把仅与j相关的项替换成y
*/

int x(int x)
{
    return 2 * sum[x];
}

int y(int x)
{
    return f[x] + sum[x] * sum[x];
}

int getk(int a , int b)
{
    return (y(a) - y(b)) / (x(a) - x(b));
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    head = 1 , tail = 0;
    for(int i = 1; i <= n; i++)
    {
        //队首出队:斜率(head,head+1)<=k,过期的决策点出队
        while(head <= tail && getk(q[head] , q[head + 1]) <= sum[i])
        {
            head++;
        }

        //动归计算
        int j = q[head];
        f[i] = f[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]);

        //队尾出队,维持单调队列的单调递增(按斜率排),为第i个节点进队做准备
        while(head <= tail && getk(q[tail] , q[tail - 1]) >= getk(q[tail] , i))
        {
            tail--;
        }

        //i进队
        q[++tail] = i;//i节点一定会进队
    }
    cout << f[n];
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值