分析:
對於輸出答案真的很好做,然後被輸出路徑噁心到了。。。
上來先離散化 + 去重簡化題目,用 \(v[i]\) 記錄權值為 \(i\) 的點,\(a[i]\) 為點 \(i\) 的權值。
那麼行徑的每一步可以分為兩類:
-
從 \(v[i]\) 內的點到 \(v[i+1]\) 的點。
-
從 \(v[i]\) 內的點到 \(v[i]\) 內的點。
設 \(dp[i]\) 為走完了所有權值小於等於 \(a[i]\) 的點後留在了 \(i\) 的最小花費。
設 \(f[i]\) 為走完了所有權值小於 \(a[i]\) 的點(以及 \(i\) 本身)後留在了 \(i\) 的最小花費。
我們發現由 \(dp[x]\) 推出 \(f[y]\) \((a[y]=a[x]+1)\) 便是第一類步,由 \(f[x]\) 推出 \(dp[y]\) \((a[x]=a[y])\)為第二類步。
第一類很好處理:
\(\min(abs(x-y),n-abs(x-y))\) 即是考慮圖是環形,\(f[y]\) 為走完了所有權值小於 \(a[y]\) 的點後留在 \(y\) 的花費,應該由一個已經處理了所有權值小於等於 \(a[y]-1\) 的地方推來的,即是屬於 \(v[a[y]-1]\) 的 \(x\) 的 \(dp[x]\)。
第二類:
請不要在意中間的三座火山,兩座活火山,一座死火山...那只是心血來潮的產物,大人總是需要我來解釋,這讓我很心累(。
顯然要求從一個點出發然後遍歷完所有的點,最後停留在一個點便是從 \(f\) 的狀態變為了 \(dp\) 的狀態了。
從 \(x\) 出發到 \(y\)。
如果 \(y\) 與 \(x\) 不相鄰,顯然要求從 \(x\) 出發轉一圈後停在與 \(x\) 相鄰的一個點後再返回 \(y\),這必然不會優於直接停留在那個與 \(x\) 相鄰的點,因此只用考慮 \(x\) 與 \(y\) 相鄰兩種情況(左右)了。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
int a[N],b[N],dp[N],f[N];
vector<int>v[N];
int main()
{
int n,cnt=0,m=0,s;
cin>>n>>s;
for(int i=1;i<=n;++i) cin>>a[i],b[++m]=a[i];
sort(b+1,b+n+1);m-=b+m+1-unique(b+1,b+n+1);
for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+m+1,a[i])-b,v[a[i]].push_back(i);
memset(dp,63,sizeof(dp));memset(f,63,sizeof(f));
for(int i=1;i<=m;++i)
{
for(auto x:v[i])
{
if(i==1) {int y=s;dp[x]=min(dp[x],min(abs(x-y),n-abs(x-y)));}
else {for(auto y:v[i-1]) dp[x]=min(dp[x],dp[y]+min(abs(x-y),n-abs(x-y)));};
}
if(v[i].size()==1) continue;
for(int j=0;j<v[i].size();++j)
{
int x=v[i][j],y,w1,w2;
if(j!=0) y=v[i][j-1],w1=dp[y]+n-(x-y);
else y=v[i][v[i].size()-1],w1=dp[y]+y-x;
if(j!=v[i].size()-1) y=v[i][j+1],w2=dp[y]+n-(y-x);
else y=v[i][0],w2=dp[y]+x-y;
f[x]=min(w1,w2);
}
for(auto x:v[i]) dp[x]=f[x];
}
int ed=0;
for(auto x:v[m]) if(dp[x]<dp[ed]) ed=x;
cout<<dp[ed]<<'\n';
return 0;
}
至於輸出路徑?只需要簡單地在記錄最小值時記錄是從哪兒推出的就行了...有些繁瑣但只需要注意點細節就行了,所以不在此贅述。
完整程式碼:Submission #265943935 - Codeforces