现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。求在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
(nleq 35000),保证数据随机
Solution
第一问很容易,只需要令 (b_i=a_i-i),然后跑最长不下降子序列即可
下面考虑第二问,令 (f[i]) 表示前 (i) 个数构成的数列要变成单调上升需要改动的最小幅度
若 (a_i,a_j, i<j) 满足 (a_j-a_i geq j-i),则 ([i,j]) 中一定存在一个 (k),使得 (a[idots k]) 都变成 (a[i]),(a[k+1dots j]) 都变成 (a[j]),整个序列上升并且花费的代价最小,暴力枚举 (j,k) 来转移,时间复杂度为 (O(n^3))
- 证明可以通过任意构造一种最优解形态,然后通过代价不变的转化使其变化为上述形态
考虑转移到 (f[i]) 的决策点 (j<i) 要求第一问中的 (g[j]+1=g[i]),因此转移点的总数是 (O(n)) 到 (O(nsqrt n)) 量级的,且 (w) 的区间期望为 (O(sqrt n)),总体复杂度估计为 (O(nsqrt n)) 到 (O(n^2)) 之间
在数组前后额外添加最大最小值,可以提供类似于超级源汇的功能
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int n,a[N],b[N],f[N],g[N],h[N];
vector <int> v[N];
int myabs(int x) {return x>0?x:-x;}
signed main() {
ios::sync_with_stdio(false);
cin>>n;
a[0]=b[0]=-1e9;a[n+1]=b[n+1]=1e9;
for(int i=1;i<=n;i++) {
cin>>a[i];
b[i]=a[i]-i;
}
++n;
h[1]=b[1]; g[1]=1;
int len=1;
for(int i=2;i<=n;i++) {
int pos=upper_bound(h+1,h+len+1,b[i])-h-1;
if(pos==len) {
++len;
h[len]=b[i];
g[i]=len;
}
else {
++pos;
h[pos]=b[i];
g[i]=pos;
}
}
for(int i=1;i<=n;i++) v[g[i]].push_back(i);
v[0].push_back(0);
f[0]=0;
for(int i=1;i<=n;i++) {
f[i]=1e9;
for(int j:v[g[i]-1]) if(j<=i&&b[i]>=b[j]) {
int tmp=0;
for(int k=j;k<=i;k++) tmp+=myabs(b[i]-b[k]);
for(int k=j;k<i;k++) {
tmp-=myabs(b[i]-b[k]);
tmp+=myabs(b[j]-b[k]);
f[i]=min(f[i],tmp+f[j]);
}
}
}
cout<<n-len<<endl<<f[n]<<endl;
}