GMOJ 1281旅行 題解
warning:此方法可能較難理解 講的爛
約定
-
除去標有“移動”,所有的 \(A\) 都為原始輸入順序。
-
\(i\text{~}j\) 的“代價”定義為 \(\sum_{i}^{j}|A_i-A_j|\) .
思路
這種題比較明顯的是DP,有兩種解法。我果然選擇了空間更差不能優化的順推法
首先,由於交換的位置從前往後,所以一個數要麼向前移動1位,要麼向後移動若干位(包括0)
用不加調料的腦回路設狀態:
\[F_{i,j}\phantom{1}(i\le j)\phantom{11}\text{表示第i個位置的數向後移到了第j個位置,1~j的最小代價} \]
假設 \(A_i\) 向後移動,那麼 \(A_{i-1}\)
然後,對於移動後 \(i\text{~}j\) 的代價,因為只有 \(A_i\) 在移動,所以 \(A_{i+1}\text{~}A_j\) 的相對位置是不變的,於是移動後 \(i+1\text{~}j\)
那麼,我們可以列出一條式子:
\[F_{i,j}=min{\{F_{k,i-1}+|A_{i+1}-A_k|\}}+S_{i+1,j}+|A_i-A_j|\phantom{11} (i<j,k<i) \]
(\(S_{i,j}\) 表示原來中 \(i\text{~} j\)的代價)
對於 \(min\) 函式內的部分,就是之前提到可以預處理的部分。
我們要注意到這條式子有一個限制條件:\((i<j)\)
對於不移動的情況,我們只需要考慮前置 \(F\) 和結尾與 \(A_i\) 的差即可:
\[F_{i,i}=min{\{F_{k,i-1}+|A_i-A_k|\}}\phantom{11} (k<i) \]
(注意 \(min\) 函式裡面的差別)
那麼,最後的答案就是列舉一下誰在最後,即:
\[Ans=min{\{F_{i,n}\}}\phantom{11} (i\le n) \]
實現
注意對於 \(i=1\) 時,\(F_{i,i}\) 和 \(min{\{F_{k,i-1}+|A_{i+1}-A_k|\}}\) 的值為0,由於不會被轉移,需要特殊處理。
程式碼
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,ans=0x7fffffff,a[2010],s[2010][2010],f[2010][2010];
void read(int &x){
char c=getchar();
for(;c<33;c=getchar());
for(x=0;(c>47)&&(c<58);x=x*10+c-48,c=getchar());
}
int main(){
read(n);
for(int i=1;i<=n;i++){
read(a[i]);
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
s[i][j]=s[i][j-1]+abs(a[j]-a[j-1]);
}
}
for(int i=1;i<=n;i++){
int temp=f[i][i]=i==1?0:0x7fffffff;
for(int j=1;j<i;j++){
temp=min(temp,f[j][i-1]+abs(a[i+1]-a[j]));
f[i][i]=min(f[i][i],f[j][i-1]+abs(a[i]-a[j]));
}
for(int j=i+1;j<=n;j++){
f[i][j]=temp+abs(a[i]-a[j])+s[i+1][j];
}
}
for(int i=1;i<=n;i++){
ans=min(ans,f[i][n]);
}
printf("%d",ans);
}