关键技术:
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.斜率优化(队列优化):
就是把决策与决策之间表示成一个类似于斜率的式子,进一步分析其中的单调性,并用队列维护其有用决策(用斜率作为标尺出队)
队首出队是为了删除过期的队首信息,队尾出队是为了维持单调队列的性质
模板题的递推公式展开之后为:
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;
}