BZOJ 1049: [HAOI2006]数字序列

1049: [HAOI2006]数字序列

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 1755  Solved: 756
[Submit][Status][Discuss]

Description

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

Input

  第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。n<=35000,保证所有数列是随机的

Output

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

Sample Input

4
5 2 3 5

Sample Output

1
4

题解

要满足将i+1到j-1的值改变可以构成严格上升序列,那么要满足a[j]-a[i]>=j-i,移项得a[i]-i<=a[j]-j,所以将a[i]-i,然后求a数组的最长不下降子序列,然后用n减去最长不下降子序列长度就是最少改变的数的个数。

因为题目中有负数,所以a[0]=-inf。

设d[i]为将前i个数变成不下降序列的最小代价。

设g[i]为以i结尾的最长不下降子序列长度,那么如果有j<i&&g[j]==g[i]-1&&a[j]<=a[i],那么说明i可以从j转移过来,如果要改变j+1到i-1的值,那么存在一个k,j<=k<i,满足改变后的值a[j+1]到a[k]等于a[j],a[k+1]到a[i-1]等于a[i]。

用邻接链表维护g[i]=u的位置,对于第i个点,枚举每个g[j]==g[i]-1的位置j,并且j要满足j<i&&a[j]<=a[i],再在j到i-1之间枚举k,用两个前缀和维护将j到k的值变成a[j]和a[i]的代价,那么转移方程为:

d[i]=min(d[i],d[j]+sum1[k]+(sum2[i-1]-sum2[k]))。

为了方便处理最后一段,加一个a[++n]=inf。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#define LL long long
using namespace std;
const int N=35005,inf=0x3f3f3f3f;
int n,mx,cnt=1;
int a[N],f[N],g[N],head[N];
LL sum1[N],sum2[N],d[N];
struct edge{
	int u,v,next;
}e[N];
void addedge(int u,int v){
	e[cnt]=(edge){u,v,head[u]};
	head[u]=cnt++;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i]-=i;
	}
	int pos;
	a[++n]=inf;
	a[0]=-inf;
	f[0]=-inf;
	for(int i=0;i<=n;i++){
		if(a[i]>=f[mx]){
			f[++mx]=a[i];
			g[i]=mx;
		}
		else{
			pos=upper_bound(f+1,f+mx+1,a[i])-f;
			f[pos]=a[i];
			g[i]=pos;
		}
	}
	addedge(g[0],0);
	for(int i=1;i<=n;i++){
		addedge(g[i],i);
		d[i]=(1LL<<60);
	}
	int v;
	for(int i=1;i<=n;i++){
		for(int j=head[g[i]-1];j;j=e[j].next){
			v=e[j].v;
			if(v>i||a[v]>a[i])continue;
			sum1[v]=sum2[v]=0;
			for(int k=v+1;k<i;k++){
				sum1[k]=sum1[k-1]+abs(a[k]-a[v]);
				sum2[k]=sum2[k-1]+abs(a[k]-a[i]);
			}
			for(int k=v;k<i;k++){
				d[i]=min(d[i],d[v]+sum1[k]+(sum2[i-1]-sum2[k]));
			}
		}
	}
	printf("%d\n%lld\n",n+1-g[n],d[n]);
	return 0;
}

转载于:https://www.cnblogs.com/chezhongyang/p/7717572.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值