Solution
题目描述
现在我们有一个长度为 n的整数序列 a。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
输入格式
第一行是一个整数,表示序列长度 n。
第二行有 n个整数,第 i个整数表示序列的第 i项 ai。
输出格式
第一行输出一个整数,表示最少需要改变多少个数。
第二行输出一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
输入输出样例
输入:4 5 2 3 5
输出:1 4
第一问
第一问题目非常easy
我们发现如果要同时保留a序列中a[i]与a[j]两个元素,则必须要满足 a[j]-a[i]≥j-i;
移项一下 我们发现 a[j]-j≥a[i]-i;
于是我们发现,我们可以开一个b数组其中对于任意元素b[i]=a[i]-i;
显然 这个b序列一定是不下降的;
于是最少改变多少个数,也就变成了要求b序列的最长不下降子序列,我们可以直接用dp来做复杂度 O(nlogn)
如果不明白我代码中写的话 请看这里
当然 也可以用树状数组来完成这一操作接下来请看第二问
第二问
第二问 我们发现一种情况是我们要解决的
每个被保留的b[i]和b[j]之间的均不合法,那么如何来改变b[i]与b[j]呢?
后面我来给个详细的证明吧 如果结合我的代码思考不出来的话,请看这位大佬的博客很详细
就是不断的缩减所谓的“台阶”,最后剩下左边的高b[i]与右边的高b[j]
最优解一定是(或者说,一定可以是)左边的b[i]到b[k]全部变成b[i]并且右边的b[k+1]到b[j]全部变成b[j]
如果最优解不是这样,我们可以无偿甚至减偿来变成这种形态。
然后枚举每个区间的k就好了
code:
#include<bits/stdc++.h> using namespace std; #define int long long #define re register const int maxn=4e4+9,INF=0x3f3f3f3f; int a[maxn],f[maxn],b[maxn],d[maxn],l[maxn],n,len=1,sum1[maxn],sum2[maxn]; vector<int> p[maxn]; signed main() { scanf("%lld",&n); for(re 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(re 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-len+1); p[0].push_back(0); b[0]=-INF;b[n+1]=INF; memset(f,INF,sizeof f); f[0]=0; for(re int i=1;i<=n+1;i++) { for(re int h=0,size=p[l[i]-1].size();h<size;++h) { int j=p[l[i]-1][h]; if(j>i||b[j]>b[i]) continue; sum1[j]=0; for(re int k=j+1;k<=i-1;k++) sum1[k]=sum1[k-1]+abs(b[k]-b[j]); sum2[i-1]=0; for(re int k=i-2;k>=j;k--) sum2[k]=sum2[k+1]+abs(b[k+1]-b[i]); for(re 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; }