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;
}