点击右上角即可分享
微信分享提示
maoyiting
音乐播放器
闲言碎语
闲言碎语
maoyiting
谨防被此博文误导 QAQ
导航
首页
留言
关于
组成
随笔分类
Others 1
算法笔记 22
题目选做 6
2022年6月 1
2022年4月 1
2021年4月 2
2021年2月 1
2020年12月 7
2020年11月 1
2020年10月 2
2020年9月 5
2020年8月 4
2020年7月 3
2020年5月 1
2020年4月 1
更多
数学 10
字符串 2
筛法 2
二分图匹配 1
动态规划 5
线性代数 2
博弈论 2
多项式 1
数据结构 4
图论 2
概率与期望 1
Luogu
Dlstxdy
莱莱(可爱的妹子)
黄队
管理
文章
联系
「HAOI 2006」数字序列
maoyiting 2020-11-24 14:42 0次浏览 2条评论 3462 字数 题目选做
首页 / 正文 分享到 :

Description

给定一长度为 n 的数列 a,可将 ai 改为任意整数 k,代价为 aik

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

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

1n3.5×104,1ai105

Solution

Part 1

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

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

  • ai<aj(显然)。

  • 改变 [i+1,j1] 内的数能够使 [i,j] 严格单调上升。所以 ajaiji。移项可得,aiiajj

构造数列 bi=aii,问题转化为求 b 的最长不降子序列。可 O(nlogn) 求得。

Part 2

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

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

所以对于 i[l,r]bi<blbi>br

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

结论:存在一个数 k[l,r]:对于 i[l,k],把 bi 改成 bl。对于 i[k+1,r],把 bi 改成 br。此时代价和最小。

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

  • n=1 时,结论显然成立。(因为 bi<blbi>brbi 改为 blbr 显然比改为取值在 [bl,br] 之间的数优)

  • n>1 时:

    • n1 个数一半改为 bl 一半改为 br:当 bn>br 时,显然将 bn 改为 br 比较优。当 bn<bl 时,若 bn 不改为 br 改为了 bl+k0kbrbl),为了使序列单调不降,前面所有改为 br 的数都应改成 bl+k(设这样的数有 x 个)。x×(br(bl+k))+((bl+k)bn)=xbr(x1)(bl+k)bnbrbn,所以此时 bn 改为 br 更优。

    • n1 个数全改为 blbr:略。

Part 3

dpi 表示最后一位是 bi 时单调不降的最小代价。

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

  • j<i,bj<bi

  • bj 结尾的最长不降子序列长度 =bi 结尾的 1

枚举分界点 k,有:

dpi=min{dpj+kp=j+1bpbj+i1p=k+1bpbi}

即:对于 p[j+1,k],将 bp 改为 bj。对于 p[k+1,i1],将 bp 改为 bi

前缀和优化转移即可。

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\n%lld\n",n-len,dp[n]);
    return 0;
}

 

上一篇 下一篇
发表评论
表情
发表评论 修改评论
Powered by vue on cnblogs