洛谷P2501 bzoj1049 [HAOI2006]数字序列

题目链接

bzoj
洛谷

题解

第一问:

假如 \(i < j\)
如果 \(j\)能从\(i\)转移过来
显然中间空隙必须足够
例如:\(50\) \(53\) \(53\) \(52\)
就不能留下\(50\)\(52\)

那么可以得到如果\(j\)能从\(i\)转移过来
满足
\(a[j]-a[i] >= j-i\)
\(=>\) \(a[j]-j >= a[i]-i\)

那么可以对于以上数列跑一边最长不下降子序列

第二问:

将原序列变为上升序列,就等于将新的序列(\(b_i = a_i-i\))变成不下降序列

那么对于最长不下降子序列相邻两点 \(c_{i-1},c_i\)

显然\(b\)序列上, 在这两点之间的点不会存在 \(c_{i-1}<=x<=c_i\)

可以证明,存在一个\(k\)点, 使得 在\(c_{i-1}\)\(k\)点之间的点变成\(c_{i-1}\),剩下的点变成\(c_i\)
代价最小

枚举\(k\),算出最小值即可

注意最长不下降子序列不只有一种,需要对于每一种都做一次

Code
#include<bits/stdc++.h>
#define MAX 35010
using namespace std;
inline int read() {
    register int x=0,t=1;
    register char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-'){t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*t;
}
struct Line {
    int v,next;
}e[MAX<<2];
int h[MAX],cnt=1,n,H[MAX],Q[MAX],len;
inline void Add(int u,int v) {
    e[cnt]=(Line){v,h[u]};
    h[u]=cnt++;
}
int f[MAX];
long long g[MAX],sum1[MAX],sum2[MAX];
int main()
{
    n=read();
    for(int i=1;i<=n;++i)H[i]=read()-i;
    H[++n]=1<<30;
    for(int i=1;i<=n;++i) {
        int l=1,r=len,ans=0;
        while(l<=r) {
            int mid=(l+r)>>1;
            if(Q[mid]>H[i])r=mid-1,ans=mid;
            else l=mid+1;
        }
        if(!ans)ans=++len;
        Q[f[i]=ans]=H[i];
    }
    printf("%d\n",n-len);
    for(int i=n;i>=0;--i)
        g[i]=1e18,Add(f[i],i);
    g[0]=0;H[0]=-H[n];
    for(int i=1;i<=n;++i) {
        for(int E=h[f[i]-1];E;E=e[E].next) {
            int v=e[E].v;
            if(v>i)break;
            if(H[v]>H[i])continue;
            for(int j=v;j<=i;++j) {
                sum1[j]=sum1[j-1]+abs(H[j]-H[v]);
                sum2[j]=sum2[j-1]+abs(H[i]-H[j]);
            }
            for(int j=v;j<i;++j)
                g[i]=min(g[i],g[v]+sum1[j]-sum1[v]+sum2[i]-sum2[j]);
        }
    }
    printf("%lld\n",g[n]);
    return 0;
}

转载于:https://www.cnblogs.com/zzy2005/p/10143547.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值