[HAOI2006] 数字序列

现在我们有一个长度为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;
}

原文地址:https://www.cnblogs.com/mollnn/p/12384566.html