现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。求在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
\(n\leq 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[i\dots k]\) 都变成 \(a[i]\),\(a[k+1\dots j]\) 都变成 \(a[j]\),整个序列上升并且花费的代价最小,暴力枚举 \(j,k\) 来转移,时间复杂度为 \(O(n^3)\)
- 证明可以通过任意构造一种最优解形态,然后通过代价不变的转化使其变化为上述形态
考虑转移到 \(f[i]\) 的决策点 \(j<i\) 要求第一问中的 \(g[j]+1=g[i]\),因此转移点的总数是 \(O(n)\) 到 \(O(n\sqrt n)\) 量级的,且 \(w\) 的区间期望为 \(O(\sqrt n)\),总体复杂度估计为 \(O(n\sqrt 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;
}
原文地址:https://www.cnblogs.com/mollnn/p/12384566.html
时间: 2024-11-10 07:11:44