【动态规划】数字序列

104 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

[HAOI2006]数字序列

题目描述

现在我们有一个长度为 nn 的整数序列 aa。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。

输入格式

第一行是一个整数,表示序列长度 nn
第二行有 nn 个整数,第 ii 个整数表示序列的第 iiaia_i

输出格式

第一行输出一个整数,表示最少需要改变多少个数。

第二行输出一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

样例 #1

样例输入 #1

4
5 2 3 5

样例输出 #1

1
4

提示

数据规模与约定

  • 对于 90%90\% 的数据,保证 n6×103n \leq 6 \times 10^3
  • 对于 100%100\% 的数据,保证 1n3.5×1041 \leq n \leq 3.5 \times 10^41ai1051 \leq a_i \leq 10^5。数据保证 aia_i 随机生成。

我们假设 i,j,(j>i) 分别为两个不被修改的相邻点,那么他们之间所有点必然要么高于 jj 要么低于 ii。我们先对每个点分析,如果这个点高于 jj 我们最少把他移动到 jj 一样的高度就好了,这对他而言是最短距离。如果这个点低于 ii 那么我最少把他移动到 ii 一样的高度就好了,这对他而言是最短距离。不管实际应该怎么移动,实际移动一定是在这个移动的基础上再移动。

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
#define int long long
const int N=4e4+9,INF=0x3f3f3f3f;
int a[N],f[N],b[N],d[N],l[N],n,len=1,sum1[N],sum2[N];
vector<int> p[N];
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i]-i;
	d[1]=b[1];b[n+1]=INF;
	l[1]=1;
	p[1].push_back(1);
	for(int i=2;i<=n+1;i++)
	{
		if(d[len]<=b[i]) 
		{
			d[++len]=b[i];
			l[i]=len;
			p[len].push_back(i);
		}
		else
		{
			int pos=upper_bound(d+1,d+1+len,b[i])-d;
			d[pos]=b[i];
			l[i]=pos;
			p[pos].push_back(i);
		}
	}
	printf("%lld\n",n-len+1);//0 1 1 2 3 4
	p[0].push_back(0);
	b[0]=-INF;b[n+1]=INF;
	memset(f,INF,sizeof f);
	f[0]=0;
	for(int i=1;i<=n+1;i++)
	{
		for(auto j:p[l[i]-1])
		{//0 0 2 1 3 4
			// printf("%lld ",j);
			if(j>i||b[j]>b[i]) continue;
			sum1[j]=0;
			for(int k=j+1;k<=i-1;k++)
				sum1[k]=sum1[k-1]+abs(b[k]-b[j]);
			sum2[i-1]=0;
			for(int k=i-2;k>=j;k--)
				sum2[k]=sum2[k+1]+abs(b[k+1]-b[i]);
			for(int k=j;k<=i-1;k++)
				f[i]=min(f[i],f[j]+sum1[k]+sum2[k]);
		}
	}

	printf("%lld",f[n+1]);
	return 0;
}