洛谷 P2501 [HAOI2006]数字序列 解题报告

P2501 [HAOI2006]数字序列

题目描述

现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。

输入输出格式

输入格式:

第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。

输出格式:

第一行一个整数表示最少需要改变多少个数。

第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

说明

90%的数据n<=6000。

100%的数据n<=35000。

保证所有数列是随机的。


“数据随机”==乱搞 啊哈

陷入了笛卡尔树的坑里

看了题解,大家一致认为第一问灰常简单,第二问灰常毒瘤

我:1394419-20180830143119407-1066792221.jpg

好吧,第一问其实有思想的,发现直接求要改变的死活不好弄,不妨使用补集转换的思想,求最多不改变的数字

\(dp_i\)代表以\(i\)为末尾的数字不改变时的最大不改变数字

转移有:

\(dp_i=max_{a_i-a_j \ge i-j} dp_j +1\)

复杂度是\(O(N^2)\)

我们发现,其实我们是在最大化转移次数

如果把转移条件移项\(a_i-i \ge a_j-j\)

\(b_i=a_i-i\),问题就转换成了求\(LIS\),可以\(O(nlogn)\)求解

第二问 有点微妙 实质上是一个跑不满的\(O(N^3)\)做法,上界极其宽松(当然要写的好才行)

\(a\)变得单调上升,等价与把\(b\)变的单调不降,花费是等价的

\(f_i\)为把前\(i\)项合法的最小花费

转移有:

\(f_i=min_{dp_i==dp_j+1} f_j+cost_{i,j}\)

先不考虑如何计算花费,考虑卡枚举前一维的常数

很显然前\(i\)项是要跑满的,从哪里转移我们建一个链表就表示转移集合

考虑如何计算费用

发现如果可以转移,那所有的在\(b_i\)\(b_i\)之间的\(b\)没有值是夹在它们中间的。

它们一定会往两端进行靠拢,可以证明(没看懂原证明),存在一个\(k\),使\(b_i\)~\(b_k\)都为\(b_i\),使\(b_k+1\)~\(b_j\)都为\(b_j\)

所有我们只需要枚举中间的这个\(k\)就行啦

代码细节还是很多的,没给值域还是很坑的


Code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
ll min(ll x,ll y){return x<y?x:y;}
ll abs(ll x){return x>0?x:-x;}
const ll N=35002;
ll n,a[N],b[N],g[N],k;
std::vector <ll > dx[N];ll dp[N],s1[N],s2[N];
int main()
{
    scanf("%lld",&n);dp[0]=-0x3f3f3f3f,b[n+1]=-dp[0],b[0]=dp[0];
    for(ll i=1;i<=n;i++)
        scanf("%lld",a+i),b[i]=a[i]-i;
    for(ll i=1;i<=n;i++)
    {
        if(b[i]>=dp[k]) dp[++k]=b[i],g[i]=k;
        else
        {
            g[i]=std::upper_bound(dp+1,dp+1+k,b[i])-dp;
            dp[g[i]]=b[i];
        }
    }
    printf("%lld\n",n-k);g[++n]=k+1;
    for(ll i=0;i<=n;i++) dx[g[i]].push_back(i);
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    for(ll i=1;i<=n;i++)
    {
        for(ll j=0;dx[g[i]-1][j]<i&&j<dx[g[i]-1].size();j++)
        {
            ll to=dx[g[i]-1][j];
            if(b[i]<b[to]) continue;
            for(ll l=to;l<=i;l++) s1[l]=abs((ll)(b[l]-b[to])),s2[l]=abs((ll)(b[l]-b[i]));
            for(ll l=to+1;l<=i;l++) s1[l]+=s1[l-1],s2[l]+=s2[l-1];
            for(ll l=to;l<=i;l++)
                dp[i]=min(s1[l]-s1[to]+s2[i]-s2[l]+dp[to],dp[i]);
        }
    }
    printf("%lld\n",dp[n]);
    return 0;
}

转载于:https://www.cnblogs.com/butterflydew/p/9560372.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值