CF612F Simba on the Circle

ilibilib發表於2024-11-08

分析:

對於輸出答案真的很好做,然後被輸出路徑噁心到了。。。

上來先離散化 + 去重簡化題目,用 \(v[i]\) 記錄權值為 \(i\) 的點,\(a[i]\) 為點 \(i\) 的權值。

那麼行徑的每一步可以分為兩類:

  1. \(v[i]\) 內的點到 \(v[i+1]\) 的點。

  2. \(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])\)為第二類步。

第一類很好處理:

\[f[y]=\min(dp[x]+\min(abs(x-y),n-abs(x-y)))(y\in v[a[y]-1]) \]

\(\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