• 「HAOI 2006」数字序列


    Description

    给定一长度为 (n) 的数列 (a),可将 (a_i) 改为任意整数 (k),代价为 (mid a_i-kmid)

    问最少改变多少个数能把它变成一个单调严格上升的序列。

    输出最少需要改变的数的个数,以及在改变的数最少的情况下,最小的代价和。

    (1leq nleq 3.5 imes 10^4,1leq a_ileq 10^5)

    Solution

    Part 1

    Solve Problem 1:需要改变的数最少,则需保留的数要尽可能多。考虑取一个补集,问题转化为求最多保留多少个数。

    对于两个数 (a_i,a_j)(不妨设 (i<j)),若可同时保留 (i)(j),则 (a_i,a_j) 需满足:

    • (a_i<a_j)(显然)。

    • 改变 ([i+1,j-1]) 内的数能够使 ([i,j]) 严格单调上升。所以 (a_j-a_igeq j-i)。移项可得,(a_i-ileq a_j-j)

    构造数列 (b_i=a_i-i),问题转化为求 (b) 的最长不降子序列。可 (O(nlog n)) 求得。

    Part 2

    Solve Problem 2:使 (a) 单调上升的代价,就是使 (b) 单调不降的代价。

    考虑在 (b) 的最长不降子序列中,任意两个相邻的元素。设它们 (b) 中的位置分别为 (l,r),则一定 不存在 (iin[l,r]),使得 (b_lleq b_ileq b_r)。否则取上 (i),保证合法,而且可以使最长不降子序列更长。

    所以对于 (forall iin[l,r])(b_i<b_l)(b_i>b_r)

    考虑如何改变 (b_i) 的值,能使序列合法且代价和最小。

    结论:存在一个数 (kin [l,r]):对于 (forall iin[l,k]),把 (b_i) 改成 (b_l)。对于 (forall iin[k+1,r]),把 (b_i) 改成 (b_r)。此时代价和最小。

    假设 ([l,r]) 之间有 (n) 个数。

    • (n=1) 时,结论显然成立。(因为 (b_i<b_l)(b_i>b_r)(b_i) 改为 (b_l)(b_r) 显然比改为取值在 ([b_l,b_r]) 之间的数优)

    • (n>1) 时:

      • (n-1) 个数一半改为 (b_l) 一半改为 (b_r):当 (b_n>b_r) 时,显然将 (b_n) 改为 (b_r) 比较优。当 (b_n<b_l) 时,若 (b_n) 不改为 (b_r) 改为了 (b_l+k)(0leq kleq b_r-b_l)),为了使序列单调不降,前面所有改为 (b_r) 的数都应改成 (b_l+k)(设这样的数有 (x) 个)。(x imes (b_r-(b_l+k))+((b_l+k)-b_n)=xb_r-(x-1)(b_l+k)-b_ngeq b_r-b_n),所以此时 (b_n) 改为 (b_r) 更优。

      • (n-1) 个数全改为 (b_l)(b_r):略。

    Part 3

    ({dp}_i) 表示最后一位是 (b_i) 时单调不降的最小代价。

    枚举 (j),枚举的 (j) 需满足:

    • (j<i,b_j<b_i)

    • (b_j) 结尾的最长不降子序列长度 (=)(b_i) 结尾的 (-1)

    枚举分界点 (k),有:

    (displaystyle{dp}_i=min{{dp}_j+sumlimits_{p=j+1}^kmid b_p-b_jmid+sumlimits_{p=k+1}^{i-1}mid b_p-b_imid})

    即:对于 (pin[j+1,k]),将 (b_p) 改为 (b_j)。对于 (pin[k+1,i-1]),将 (b_p) 改为 (b_i)

    前缀和优化转移即可。

    Code

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=4e4+5;
    int n,a[N],b[N],len,f[N],g[N],dp[N],pre[N],suf[N];
    vector<int>v[N];    //v[i]: 记录长度为 i 的最长不降子序列的结尾
    void solve(int l,int r,int L,int R){
        pre[l]=suf[r+1]=0;
        for(int i=l+1;i<=r;i++)    //前缀和 
            pre[i]=pre[i-1]+abs(b[i]-L);
        for(int i=r;i>=l+1;i--)    //后缀和 
            suf[i]=suf[i+1]+abs(b[i]-R); 
    }
    signed main(){
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]),b[i]=a[i]-i;
        b[0]=-1e9,b[++n]=1e9;     //边界。加上前后最小值最大值方便操作。最大值不要设太大,不然算前缀和的时候可能会爆。 
        f[1]=b[1],len=1,g[1]=1,v[1].push_back(1); 
        for(int i=2;i<=n;i++){    //O(n log n) 求最长不降子序列 
            if(b[i]>=f[len]) f[++len]=b[i],g[i]=len;
            else{
                int x=upper_bound(f+1,f+1+len,b[i])-f;
                f[x]=b[i],g[i]=x;    //g[i]: 以第 i 个数结尾的最长不降子序列的长度 
            }
        }
        for(int i=0;i<=n;i++) v[g[i]].push_back(i); 
        memset(dp,0x3f,sizeof(dp)),dp[0]=0;
        for(int i=1;i<=n;i++)
            for(int p=0;p<(int)v[g[i]-1].size();p++){    //如果 b[j] 要拼上前面合适的 b[i],就去前面找长度为 g[i]-1 且能拼上的 
                int j=v[g[i]-1][p];        //以 b[j] 结尾的 最长不降子序列长度 = 以 b[i] 结尾的 -1 
                if(j>i||b[j]>b[i]) continue;    //j<i,b[j]<=b[i] 才行 
                solve(j,i,b[j],b[i]);  
                for(int k=j;k<i;k++)    //枚举分界点 k 
                    dp[i]=min(dp[i],dp[j]+pre[k]+suf[k+1]);
            }
        printf("%lld
    %lld
    ",n-len,dp[n]);
        return 0;
    }
  • 相关阅读:
    git命令-切换分支
    Git SSH Key 生成步骤
    12个非常有用的JavaScript技巧
    project 2013 删除资源
    project 2013 工时完成百分比不会自动更新填充
    project 2013 显示标题
    project 2013 任务显示编号
    project 2013 设置工期为1个工作日,但开始时间与结束时间不是同一天
    ecplise properties文件 中文转码
    Jeesite 代码生成
  • 原文地址:https://www.cnblogs.com/maoyiting/p/14028986.html
Copyright © 2020-2023  润新知