校门外的树(单调队列优化区间DP)

题目描述
校门外刚植了一排树,但是这些树高度不一,很影响美观。假设有 n颗树,第 i颗数的
高度为 ℎ? cm。这些不美观度给学校带来的损失的计算方法为 ? × ∑?=1 ?−1 ∣ ℎ? + 1 − ℎ? ∣。  不过学校有止损方法,就是给树增高,但是给树增高的成本很大,给任意一颗树增高
? cm 的费用为 ?2。
 请你帮学校计算最少的损失是多少。
 输入格式
 第一行输入两个整数 n(1≤n≤50000,1≤C≤100) 。
 接下来一行输入 n个表示每颗数的高度 (1 ≤ ℎ? ≤ 100)。
 输出格式
 输出最小的损失。
 样例输入
 5 2
 2 3 5 1 4
 样例输出
 15

初步分析
这道题呢,我们可以想到可能要用DP来做。我们设dp[i]j来表示第i棵树长度为j时最小的损失。那么状态方程怎么写呢?
dp[i][j]=min{dp[i-1][k]+c*|j-k|+(j-h[i]^2} (h[i-1]=<k<=100,k表示第i-1棵树的高度)
所以如果我们就用这个状态方程来更新的话则应该是三重循环,依次遍历i,j,k。复杂度就是O(10000*n);对于比较大或者复杂的数据可能会过不了。
那么我们怎么来优化这个时间呢?我们看看这个方程对于每一个j,它都遍历了一遍k从h[i-1]到100.所以我们看看能不能用单调队列来记录某些重复的信息。

过程分析
我们把上面的那个状态方程分成两部分(就是把绝对值去了)。
当k<=j时 dp[i][j]=min{dp[i-1][k]-ck}+cj+(j-h[i])^2
当k > j时 dp[i][j]=min{dp[i-1][k]+ck}-c
j+(j-h[i])^2

由于只有{}里面含有k值其他的都与k无关,所以我们只需要找到{}里的最小值即可。且每次j++的时候,两个式子的k能搜索到的范围都会变。
对于第一个式子,我们用一个min_number来记录所有k<=j时,所记录到的最小的dp[i-1][k]-ck值,然后每当j增大一时。k也增大一,如果当前的dp[i-1][j]比min_number小的话我们就更新它。
对于第二个式子,我们用一个单调递减的队列来记录,首先通过第一次的j=h[i]时的初始化,我们就把单调队列更新完。然后再进行j从h[i]+1开始遍历,且每次由于j增大1,k>j,所以k包含的范围就会变小。所以需要用q[l].id去更新队首元素看是否满足q[l].id>j

关键点
所有的动态规划问题都必须注意两点,1是状态方程的正确性。2是初始化问题。这道题对于每一个i值我们都需要创建一个空队列,因为队列是拿来更新j的状态的。并且我们需要朴素遍历(就用初始的状态方程)一次来更新min_number的值和单调队列的值。然后再用第二个状态方程。

直接上代码

#include <iostream>
#include <math.h>
using namespace std;
const int MAX_N=50010;
int n,c;

int dp[MAX_N][101];
int h[MAX_N];
const int inf=0x3f3f3f3f;


struct queue{
    int id;
    int value;
    queue(){};
    queue(int id,int value){
        this->id=id;
        this->value=value;
    }
}q[MAX_N];
int l=0,r=-1;

void init(){

    for(int i=1;i<=n;++i){
        for(int j=0;j<=100;++j){
            dp[i][j]=inf;
        }
    }

    for(int i=h[1];i<=100;++i){
        dp[1][i]=(i-h[i])*(i-h[i]);
    }

}
int main() {
    scanf("%d %d",&n,&c);
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
    }

    init();

    for(int i=2;i<=n;++i){
        l=0;
        r=-1;
        int min_number=inf;

        int j=h[i];
        for(int k=h[i-1];k<=100;++k){
            if(k<=j){
                min_number=min(min_number,dp[i-1][k]-c*k);
                dp[i][j]=min(dp[i][j],dp[i-1][k]-c*k+c*j+(j-h[i])*(j-h[i]));
            }
            else if(k>j){
                dp[i][j]=min(dp[i][j],dp[i-1][k]+c*k-c*j+(j-h[i])*(j-h[i]));
                while(l<=r && q[r].value>dp[i-1][k]+c*k) r--;
                q[++r]=queue(k,dp[i-1][k]+c*k);
            }
        }

        for(int j=h[i]+1;j<=100;++j){
            if(h[i-1]<=h[i]){
                if(dp[i-1][j]-c*j<min_number){
                    min_number=dp[i-1][j]-c*j;
                }
                dp[i][j]=min_number+c*j+(j-h[i])*(j-h[i]);
            }

            while(l<= r && q[l].id<=j) l++;
            dp[i][j]=min(dp[i][j],q[l].value-c*j+(j-h[i])*(j-h[i]));
        }
    }

    int answer=inf;
    for(int i=h[n];i<=100;++i){
        answer=min(answer,dp[n][i]);
    }

    cout<<answer;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值