给出一个数组,
vali..n
求一个该数组排列,
ord1..n
,满足
∀i∈[1,n),valordi≤valordi+1
,并且使得
∑i=1n−1abs(ordi−ordi+1)
最小
首先很容易的就能想因为要满足排列之后数非降,我们能调整的只有
val
相同的位置的顺序
然后一个能想到的想法是对输入的数进行离散化,然后建立
m(m是离散化之后的数的数量)
层的图,每一层的点满足
val
相同,从高到低
val
递增,只有上下层或者同一层的点之间连有一条边,边权为两个点之间的距离,然后跑一遍最短哈密顿路径就好了
理想很美好,但是最短哈密顿路径是一个知名的 NP 问题,所以还需要优化一下
你可能会注意到,每一层的点之中只有两个端点是最重要的,只要保证你访问过这两个端点,那么这一层的点其实你都能经过一遍(想一想,为什么)
然后我们就可以把每一层不是端点的点都丢掉了
也就是说,现在每一层我们都只有两个点了
然后的dp过程就很trivial了
设
dpi,0和dpi,1
分别为在第
i
层并且在左/右端点访问完这一层所有其他点的花费,然后就可以很开心的
我是代码的分割线
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 112345;
#define LL long long
vector<int> node[maxn];
int arr[maxn];
int val[maxn];
LL dp[maxn][2];
LL loca[maxn][2];
void init(int n){
for(int i=0;i<=n;i++){
node[i].clear();
}
}
int main(){
int n;
while(~scanf("%d",&n)){
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
val[i] = arr[i];
}
val[n] = 0;
int bound = n+1;
sort(val,val+bound);
bound = unique(val,val+bound) - val;
for(int i=0;i<n;i++){
arr[i] = lower_bound(val,val+bound,arr[i])-val;
}
init(n);
for(int i=0;i<n;i++){
node[arr[i]].push_back(i);
}
for(int i=1;i<bound;i++){
sort(node[i].begin(),node[i].end());
loca[i][0] = node[i][0];
loca[i][1] = node[i][node[i].size()-1];
}
memset(dp,0x3f,sizeof(dp));
dp[0][0] = dp[0][1] =0;
loca[0][0] = loca[0][1] = 0;
for(int i=1;i<bound;i++){
LL lsize = abs(loca[i][0] - loca[i][1]);
LL & b0 = loca[i-1][0];
LL & b1 = loca[i-1][1];
LL & a0 = loca[i][0];
LL & a1 = loca[i][1];
dp[i][0] = min(dp[i-1][0]+min(lsize + abs(b0 - a1),lsize * 2 + abs(b0-a0)),
dp[i-1][1]+min(lsize + abs(b1 - a1),lsize * 2 + abs(b1-a0)));
dp[i][1] = min(dp[i-1][0]+min(lsize + abs(b0 - a0),lsize * 2 + abs(b0-a1)),
dp[i-1][1]+min(lsize + abs(b1 - a0),lsize * 2 + abs(b1-a1)));
}
printf("%lld\n",min(dp[bound-1][0],dp[bound-1][1])+n);
}
return 0;
}