vijos1661——旅行-2

vijos1661
我能说这破题坑了我一上午???
就因为一个东西手滑写在了min里面。。。。

这道题如果正着考虑,情况太tmd多了,因为可以连续施法。。。
然而如果倒过来,我们认为连着j次施法,就是让一个i位置上的数字移动到i+j位置来,那么改变的信息其实不多。

所以我们可以枚举i和j,然后来设计一下状态

f[i][0]表示i这个点没有做过移动的最小代价
f[i][1]表示i这个点做过移动的最小代价
因为每个点不能重复施法,所以如果一个点做过移动,那么当前位置上一定是i+1

状态转移,f[i][0]=min(f[i+1][0]+abs(a[i]-a[i+1]),f[i+1][1]+abs(a[i]-a[i+2))
这个好处理。。。
接下来是f[i][1],我们假设它移动到j的位置,大家可以画图理解一下。
f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[i]-a[j+1]),f[j+1][1]+abs(a[i]-a[j+2]))+abs(a[i]-a[j])+sum[j]-sum[i+1]);

这里解释一下sum,sum[i]表示1~i不做任何处理的代价,那么这个显然满足区间减法,我们把i移到j,中间一大堆的代价显然不变,所以可以直接前缀和处理。

以及我们要特殊处理j=n的情况。。很显然(大雾
f[i][1]=min(f[i][1],sum[n]-sum[i+1]+abs(a[i]-a[n]));

破题害人不浅!
注意我们可能枚举到a[n+1],所以a[n+1]要赋为a[n]

CODE:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<string>
#include<map>
#include<cstring>
#include<vector>
#define inf 1e9
#define ll long long
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Dow(i,j,k) for(int i=k;i>=j;i--)
using namespace std;
int f[5001][2],a[5001],n,sum[5001];
int main()
{
    scanf("%d",&n);
    For(i,1,n)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+(i==1?0:1)*abs(a[i]-a[i-1]);
    a[n+1]=a[n];
    For(i,1,n*2)    f[i][1]=f[i][0]=inf;
    f[n][1]=f[n][0]=0;
    Dow(i,1,n-1)
    {
        f[i][0]=min(f[i+1][1]+abs(a[i+2]-a[i]),f[i+1][0]+abs(a[i+1]-a[i])); 
        For(j,i+1,n-1)
            f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[i]-a[j+1]),f[j+1][1]+abs(a[i]-a[j+2]))+abs(a[i]-a[j])+sum[j]-sum[i+1]);
        f[i][1]=min(f[i][1],sum[n]-sum[i+1]+abs(a[i]-a[n]));
    }
    printf("%d\n",min(f[1][1],f[1][0]));
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值