题目描述
校门外刚植了一排树,但是这些树高度不一,很影响美观。假设有 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}-cj+(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;
}