JZOJ 1281.旅行

题目大意

一个数列a[ i i i],可以无代价交换前后两个数,且交换的两个数值需位于之前两个数值之后,即如果某次交换了a[ j j j]和a[ j + 1 j+1 j+1],那么下次交换a[ k k k]和a[ k + 1 k+1 k+1]必须满足 j < k j<k jk,问
∑ i = 2 n ∣ a [ i ] − a [ i − 1 ] ∣ \sum_{i=2}^n |a[i]-a[i-1]|\qquad i=2na[i]a[i1]

思路

很直接的想法是枚举每个二进制状态,并比较得出最小值,期望得分20分。
但是我们可以从数据范围看出这是一个二维dp (不要问我是怎么看出来的) 。考场尚未推出状态转移方程,考后参考别人的程序才想出,这其实是一道dp好题。
定义f[ i i i][0]为第 i i i个值不与后面的交换消耗的最小体力,f[ i i i][1]为第 i i i个值与后面交换消耗的最小的最小体力,sum[ i i i]为未交换时消耗的体力。
状态转移方程:
1.当第 i i i个值不与后面的值交换时,此时消耗的最小体力就是 m i n min min
(第 i + 1 i+1 i+1个值不与后面交换时的最小体力加上第 i i i个值与 i + 1 i+1 i+1个值的差值的绝对值,
i + 1 i+1 i+1个值与后面交换时的最小体力加上第 i i i个值与 i + 2 i+2 i+2个值的差值的绝对值)。
code:

f[i][0]=min(f[i+1][0]+abs(a[i+1]-a[i]),f[i+1][1]+abs(a[i+2]-a[i]));

2.当第 i i i个值与后面的值交换时,此时消耗的最小体力就与第 i i i个值具体被换到哪里有关。设第 j j j个值与第 i i i个值交换,此处较难理解,用图片具体说明:
i与j交换前:
i与j交换前
i与j交换后:
i与j交换后
这里简单地用 i i i表示a[ i i i]的值(其他依次类推)。设第 i i i个位置的值与第 i i i个位置的值交换,交换后消耗的最小体力其实就是a[ j j j]与a[ i i i]的差值的绝对值加上第 i + 1 i+1 i+1个位置和第 j j j个位置中间的差值再加上一个类似于f[ j j j][0]的式子,只是这里因为原来a[ j j j]的位置现在是a[ i i i],所以将f[ j j j][0]式子中的 j j j换成 i i i。特别的,与第 n n n个值交换,因为f[ n n n][0]和f[ n n n][1]都是0,所以f[ i i i][0]可直接由a[ n n n]与a[ i i i]的差值的绝对值加上第 i + 1 i+1 i+1个位置和第 n n n个位置中间的差值得到。
code:

for(ll j=i+1;j<=n-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]))+sum[j]-sum[i+1]+abs(a[i]-a[j]));
f[i][1]=min(f[i][1],sum[n]-sum[i+1]+abs(a[n]-a[i]));
完整code:
#include<bits/stdc++.h>
#define ll long long
#define R register
using namespace std;
inline ll read(){
	ll f=0,pa=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')pa=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){f=(f<<3)+(f<<1)+(ch^48);ch=getchar();}
	return f*pa;
}
inline void write(ll x){
	if(x<0)x=-x,putchar('-');
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
ll n,a[2005],sum[2005],f[2005][2],cha;
inline ll abss(ll a){return a>=0?a:-a;}
inline ll minn(ll a,ll b){return a>b?b:a;}
int main(){
	n=read();
	for(R ll i=1;i<=n;i++){
		a[i]=read();f[i][1]=f[i][0]=0x7fffffff;
		sum[i]=sum[i-1]+abss(a[i]-a[i-1]);
	}
	f[n][0]=f[n][1]=0;a[n+1]=a[n];
	for(R ll i=n-1;i>=1;i--){
		f[i][0]=minn(f[i+1][0]+abss(a[i+1]-a[i]),f[i+1][1]+abs(a[i+2]-a[i]));
		for(R ll j=i+1;j<=n-1;j++)
			f[i][1]=minn(f[i][1],minn(f[j+1][0]+abss(a[i]-a[j+1]),f[j+1][1]+abss(a[i]-a[j+2]))+sum[j]-sum[i+1]+abss(a[i]-a[j]));
		f[i][1]=minn(f[i][1],sum[n]-sum[i+1]+abss(a[n]-a[i]));
	}
	write(minn(f[1][0],f[1][1]));
	return 0;
}

p.s. 听说自己手写函数会比调用自带的函数跑得快,建议各位自己手写函数(虽然这题没必要)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值