当前位置: 代码迷 >> 综合 >> JZOJ 1281. 旅行【dp】
  详细解决方案

JZOJ 1281. 旅行【dp】

热度:3   发布时间:2024-03-07 08:16:02.0

233

  • 题意:
  • 分析:
  • 代码:


题意:

给出一个序列aia_iai?,当想要经过i?i+1i\sim i+1i?i+1这段路时,花费为∣ai?ai+1∣|a_i-a_{i+1}|ai??ai+1?
但我们可以选择任意相邻的位置进行交换aia_iai?ai+1a_{i+1}ai+1?,只有一个限制,即每次交换的ai、ai+1a_i、a_{i+1}ai?ai+1?必须在上次交换的ak、ak+1a_k、a_{k+1}ak?ak+1?后,也就是k<ik<ik<i
问花费和最少是多少


分析:

根据题目保证的交换条件,嗯,这很dpdpdp
其实想着dpdpdp,后来又想搞个贪心偷鸡,但正确性一下就锅了,却给我提供了一个很有用的结论
对于一个位置iii,要么往前一位,要么就往后任意位,然后yyyyyy一下交换的过程,可以发现,若设交换后的位置是jjj,那花费真正产生变化的只有i?1?ii-1\sim ii?1?ij?j+1j\sim j+1j?j+1,在这中间的i?ji\sim ji?j是不会产生变化的,所以可以预处理下
fi,jf_{i,j}fi,j?表示aia_iai?交换到了aja_jaj?的位置的最少花费,转移的时候记录下之前状态转移到第iii位的最小值,然后枚举之后的位置jjj,设si,js_{i,j}si,j?表示i?ji\sim ji?j的花费,也就是之前预处理的那个东东,转移过去的花费就可以表示成
fi,j=min+si+1,j+∣aj?ai∣f_{i,j}=min+s_{i+1,j}+|a_j-a_i|fi,j?=min+si+1,j?+aj??ai?
最后答案就去找交换到nnn的上一次位置在哪里,取最小值


代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
#define LL long long 
using namespace std;
inline int read() {
    int d=0,f=1;char s=getchar();while(s<'0'||s>'9'){
    if(s=='-')f=-1;s=getchar();}while(s>='0'&&s<='9'){
    d=d*10+s-'0';s=getchar();}return d*f;
}
LL a[2005],f[2005][2005],s[2005][2005];
int main()
{
    LL n=read();for(LL i=1;i<=n;i++) a[i]=read();for(LL i=1;i<n;i++) for(LL j=i+1;j<=n;j++) s[i][j]=s[i][j-1]+abs(a[j]-a[j-1]);memset(f,0x3f,sizeof f);for(LL i=1;i<=n;i++) f[1][i]=s[2][i]+abs(a[1]-a[i]);for(LL i=2;i<=n;i++){
    LL mx=2147483647;for(LL j=1;j<i;j++){
    f[i][i]=min(f[i][i],f[j][i-1]+abs(a[i]-a[j]));	mx=min(mx,f[j][i-1]+abs(a[i+1]-a[j]));}for(LL j=i+1;j<=n;j++) f[i][j]=min(f[i][j],mx+s[i+1][j]+abs(a[i]-a[j]));}LL ans=2147483647;for(LL i=1;i<=n;i++) ans=min(ans,f[i][n]);cout<<ans;return 0;
}