bzoj2439[中山市选2011] 序列 DP

168 篇文章 0 订阅
73 篇文章 0 订阅

Description

小 W 很喜欢序列,尤其喜欢“W”形的和“M”形的序列。定义“M”形
的序列为一个长度为 T 的序列{Si},满足:存在 1 < x < y < z < N,使得S1 < … <
Sx > … > Sy < … < Sz > … > ST。
一天他看到了一个长度为N 的整数序列{Ai},他想通过一些修改把序列变成
“M”形的。但这时小 X 过来了,说这个序列是他的,小 W 如果想要修改就要
支付一定的费用。每支付一单位的费用,小 W 都可以进行这样的操作:将一段
连续的数同时加上 1,即选定i, j 满足1 ≤ i ≤ j ≤ N 并令Ai, Ai+1, …, Aj均加上1。
小 W 想用最小的费用将序列变成“M”形的。但是有个条件:如果他修改
成的目标是序列{Bi}满足B1 < … < Bx > … > By < … < Bz > … > BN, 那么必须有Ay=
By。
现在,他希望你来帮他计算最小费用。

Input

第一行包含一个整数 N,表示序列A的长度。
第二行有 N 个整数给出初始的序列{Ai}。

Output

仅包含一行,为最小的花费。

Sample Input

5

2 1 2 2 3
Sample Output

4
HINT

对于100%的数据满足 5 ≤ N ≤ 100 000,0 ≤ Ai ≤ 10 ^9。

Source

好像没什么人写题解= =那我就来一发吧。
首先有一个很显然的想法(网上也有人说过)
设f[i]表示1-i变为递增序列的代价,g[i]表示i-n变为递减序列的代价
一个递增+一个递减=一个单峰
所以设f1[i]表示1-i变为单峰序列的代价,那么明显有f1[i]=min(f1[i],max(f[j]-f[1],g[j]-g[i]))j=2….i-1
g1同理。
目标序列=一个单峰+一个单峰
那么ans=min(ans,f1[i]+g1[i]);
目前为止没想出优化,所以只会n^2。
先贴个代码(暴力n^2)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll f[N],f1[N],g1[N],g[N],a[N];
ll mxl[N],mxr[N];
int n;
int main()
{
    scanf("%d",&n);
    fo(i,1,n)
    {
        scanf("%lld",&a[i]);
    }
    ll ans=1e15;
    fo(i,1,n)f[i]=f[i-1]+max((ll)0,a[i-1]-a[i]+1);
    fd(i,n,1)g[i]=g[i+1]+max((ll)0,a[i+1]-a[i]+1);
    //fo(i,1,n)printf("%lld %lld\n",f[i],g[i]);
    //int mn=;
    fo(i,3,n)
    {
        f1[i]=1e15;
        fo(j,2,i)
        f1[i]=min(f1[i],max(f[j]-f[1],g[j]-g[i]));
    }
    //mx=id=0;
    fd(i,n-2,1)
    {
        g1[i]=1e15;
        fo(j,i,n-1)
        g1[i]=min(g1[i],max(f[j]-f[i],g[j]-g[n]));
    }
    fo(i,3,n-2)
    ans=min(ans,f1[i]+g1[i]);
    printf("%lld\n",ans);
} 

正解代码在下面,为什么等我吃完饭回来写。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~分割线
好了回来了= =,这个正解的思想和上面的暴力是一样的,只不过实现方法稍微不同。
我们先预处理相邻两个数的差。
然后预处理出相邻两位的代价,也就是b,b[i][0]是正,b[i][1]是反,其实这两个东西的前缀和就是上面的f,g。
然后我们用一个指针j,还有tmp1,tmp2来存储两个前缀和,其实我们发现并不需要每一次把前面的扫一遍,只需要比较一下,如果能更新才往后走,这样就可以把时间化为线性的了。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
int n,m;
typedef long long ll;
ll f[N],g[N];
ll dec[N],b[N][2],a[N];
void dp(ll f[])
{
    a[n+1]=-1;
    fo(i,1,n)dec[i]=a[i+1]-a[i];
    fo(i,1,n)
    {
        b[i][0]=max((ll)0,1-dec[i]);
        b[i][1]=max((ll)0,dec[i]+1); 
    }
    ll tmp1=b[1][0],tmp2=b[2][1];
    f[3]=max(tmp1,tmp2);
    int j=2;
    fo(i,4,n)
    {
        tmp2+=b[i-1][1];
        while(j<i-1)
        {
            ll ttmp1=tmp1+b[j][0];
            ll ttmp2=tmp2-b[j][1];
            ll mx=max(tmp1,tmp2),tmx=max(ttmp1,ttmp2);
            if (tmx<=mx)tmp1=ttmp1,tmp2=ttmp2,j++;
            else break;
        } 
        f[i]=max(tmp1,tmp2);
    }
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n)
    scanf("%lld",&a[i]);
    dp(f);
    reverse(a+1,a+1+n);
    dp(g);
    reverse(g+1,g+1+n);
    ll ans=1e15;
    fo(i,3,n-2)
    ans=min(ans,f[i]+g[i]);
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值