kAc在数轴上有N片西瓜地。第 i片的坐标是X[i](注意 X并没有排序)。任意两片西瓜地坐标不同。有一天他要给这N片西瓜地浇水。初始他在X[1]的位置。他必须按1..N 的顺序浇水,也就是说,必须先去X[1],再去X[2]...最后到X[n](他可以沿着坐标轴正方向或者负方向走)。
给西瓜地浇水不需要花费时间。每走1单位的距离需要花费1 的时间。 现在kAc为了节约时间 打算建立K个超时空传送站。如果两个位置P、Q,在这两处位置都有超时空传送站,那么我们可以瞬间转移过去(从 P到 Q或者从Q到 P) 。
现在他想知道 如果要给这 N片西瓜地都浇好水,最短需要多少时间?
考场上推了一个要手写16个转移的dp方程直接心态爆炸打了暴力。
首先这个题目套了一个美丽的套子,让我们无从设计无后效性的状态,所以我们可以拨开这个外壳,便可以发现,其实总距离之和这条路被经过的次数有关。所以我们可以用cost[i][j]先来表示排序之后第i和j个位置放了传送门,经过i,j之间路径的最小代价。
那么第一种就是整条路经都在i,j内包含,则为从i直接到j与从i到l,再从r到j的最小值。第二种就是路径左端点在i.j内,右端点在之外,那么就是从左端点到j,或者到从左端点到i的最小值了,第三种是右端点在i到j内,左端点在l左侧,那么与第二种同理。
预处理了这样的数组,那么我们就很容易dp了,用dp[i][j]表示考虑到第i个点且第i个点用了,用了j个任意门的最小距离了,转移就是枚举前一个,加上走到的cost即可。
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 60
using namespace std;
typedef long long ll;
ll n,k;
ll dp[maxn][maxn];
ll x[maxn],b[maxn];
ll cost[maxn][maxn];
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%lld",&x[i]);
b[i]=x[i];
}
scanf("%lld",&k);
b[n+1]=-12345678900000ll;b[n+2]=12345678900000ll;
sort(b+1,b+1+n+2);
for(ll i=1;i<=n+2;i++)
{
for(ll j=i+1;j<=n+2;j++)
{
for(ll mid=2;mid<=n;mid++)
{
ll l=min(x[mid],x[mid-1]),r=max(x[mid],x[mid-1]);
if(b[i]<=l && l<=b[j] && b[i]<=r && r<=b[j])
cost[i][j]+=min(r-l,l-b[i]+b[j]-r);
else if(b[i]<=l && l<=b[j])
cost[i][j]+=min(b[j]-l,l-b[i]);
else if(b[i]<=r && r<=b[j])
cost[i][j]+=min(b[j]-r,r-b[i]);
}
}
}
memset(dp,0x3f,sizeof(dp));
dp[1][0]=0;
ll ans=123456764589ll;
for(ll i=2;i<=n+1;i++)
{
for(ll j=1;j<=k;j++)
{
for(ll pre=i-1;pre>=1;pre--)
{
dp[i][j]=min(dp[i][j],dp[pre][j-1]+cost[pre][i]);
}
if(i==n+1)
ans=min(ans,dp[i][j]);
}
}
printf("%lld\n",ans);
}