JZOJ 1281. 旅行【dp】


题意:

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


分析:

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


代码:

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值