斜率化dp

来源:互联网 发布:天谕玉虚捏脸数据分享 编辑:程序博客网 时间:2024/05/24 04:19

给你一个序列,至多或分成m段,每段有花费和限制,问符合情况的最小花费是多少。有限制的情况就是一个二维dp了,现在我们只考虑下没有限制的时候。

dp[i]表示到第i个数的时候最小的花费。则dp[i]=dp[j]+(sum[i]-sum[j])^2,(这里以求每段区间平方和最小为例)。

我们用单调队列进行维护,首先是这个元素可不可以进入队列,进入之前,队尾的元素有哪些可以被弹出来。求解时,要判断队首两个元素间谁是更好的一个解。

j比k优,则dp[j]+sum[j]^2-dp[k]+sum[k]^2<=sum[i]*2*(sum[j]-sum[k]).可转化成g[j,k]<sum[i].而当新来一个元素i时,如果g[i,j]<g[j,j-1],则当前的j可以被弹出去,继续进行判断,直到大于。而每次求dp[i]时,判断下队首两个元素谁更优些。

//n个数字,划分成几个段,每个段为这几个数的和的平方+常数m。现在求如何划分使得总和最小 
#include<iostream>
#include<cstdio>
#include<queue>
const int maxn=5*1e5+10;
int  dp[maxn],sum[maxn];
int q[maxn];//队列 
int n,m;
using namespace std;
int getdp(int i,int j)
{
return dp[j]+sum[i]*sum[i]+sum[j]*sum[j]-2*sum[i]*sum[j]+m;
}
int getup(int j,int k)
{
return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];
}
int getdown(int j,int k)
{
return 2*(sum[j]-sum[k]);
}
int main()
{
int n,m;
int head,tail;
    while(~scanf("%d%d",&n,&m))
    {
    for(int i=1;i<=n;i++)
     scanf("%d",&sum[i]);
    sum[0]=dp[0];
    for(int i=1;i<=n;i++)
     sum[i]+=sum[i-1];//sum[i]存储的是前i个数据的和
head=tail=0;
q[tail++]=0;
for(int i=1;i<=n;i++)
{
while(head+1<tail&&getup(q[head+1],q[head])<=sum[i]*getdown(q[head+1],q[head]))//求解时,如果前面的比后面的更优,就取得 
  head++;
dp[i]=getdp(i,q[head]);
while(head+1<tail&&getup(i,q[tail-1])*getdown(q[tail-1],q[tail-2])<=getup(q[tail-1],q[tail-2])*getdown(i,q[tail-1]))//如果g[i,j]<g[j,k]时,就将当前队尾元素弹出去 
 tail--;
q[tail++]=i;


printf("%d\n",dp[n]);
}
return 0;
}

待续,关于二维dp

原创粉丝点击