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

P2501 [HAOI2006]数字序列

题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

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

说明

90%的数据n<=6000。

100%的数据n<=35000。

保证所有数列是随机的。


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

陷入了笛卡尔树的坑里

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

我:

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

\(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;
}
posted @ 2018-08-30 15:17  露迭月  阅读(398)  评论(0编辑  收藏  举报