Codeforces Round #646 F - Rotating Substrings (1363F)

Rotating Substrings

題意

給定兩個等長的字符串 a,ba,b (長度至多2000),對第字符串 aa 可以做如下操作:將一個字串向右旋轉一格,問最少幾次操作將其變成 bb 字符串。

觀察

旋轉操作過於醜陋,事實上觀察容易發現,對區間 [l,r][l,r] 旋轉,等價於將 ara_r 移到 ala_l 前面,此外沒有影響。

分析

先考慮可行性,顯然如果兩個字符串各個字符數量不一致則無解,反之一定有解。

再分析如何求解最優解。事實上旋轉操作可以視爲將一個元素挑出來,移到前面的任何位置。可以發現,元素只能向前移,不能向後移,所以後面的元素一定先於前面的元素確定,考慮從後向前推。

不失一般性,考慮特定的兩位 ai,bja_i,b_j , 假設其後的內容已經被確定,考慮完成之前的內容的匹配需要的次數,記爲 dp(i,j)dp(i,j),那麼有如下三種情況

  • dp(i,j)=dp(i1,j)+1dp(i,j) = dp(i-1,j)+1 :即花費 11 的代價將 aia_i 移動到之前某個位置
  • dp(i,j)=dp(i1,j1)ifai=bjdp(i,j) = dp(i-1,j-1) \quad if\quad a_i=b_j :即兩者末位一樣,可以一起消掉
  • dp(i,j)=dp(i,j1)ifsa[n][b[j]]sa[i][b[j]]>sb[n][b[j]]sb[j][b[j]]dp(i,j) = dp(i,j-1) \quad if\quad sa[n][b[j]]-sa[i][b[j]]>sb[n][b[j]]-sb[j][b[j]] :其中 sa[p][q]sa[p][q] 表示字符串 aa 中 字符 qq 個數到 pp 爲止的前綴和。 這裏的條件即是要求 aa[i+1,n][i+1,n] 區間內字符 bjb_j 對應的字符的個數多於bb[j+1,n][j+1,n] 區間內 bjb_j 對應的字符的個數。這個操作的物理意義是將 aa 中後面的某個 bjb_j 字符移動到當前位置,並且和 bjb_j 向抵消。這步移動的操作對應之前某次進行的情況1,且代價在那時已經進行過計算。
  • dp(i,j)=0ifi=0dp(i,j) = 0 \quad if\quad i=0 :當字符串 aa 已經被全部pick過或者消去過時,那麼在滿足有解條件的情況下,一定可以匹配出對應的答案,且所消耗的操作次數之前已經計算過,所以結果就是0。

於是根據上述條件容易得到dp遞推式和邊界條件,且情況4已經囊括了所有的邊界條件,因爲 iji\leqslant j 在遞歸過程中恆成立,唯一使 iji-j 減小的情況3發生 j1j-1 的必要條件之一是 i<ji<j 。且 dp(i,j)dp(i,j) 的物理意義也容易得到:aa 字符串到 ii 爲止在塞入後面某些移過來的元素後能夠與 bb 字符串到 jj 爲止完全匹配的最少操作次數。

代碼

得到dp式子以後容易編碼,可以用記憶化搜索實現。事先對前綴和做預處理即可。

總共狀態數是 n2n^2 ,每個狀態轉移需要 O(1)O(1) 時間,所以總複雜度是 O(n2)O(n^2)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int inf=1e9+7;
int sa[2005][26],sb[2005][26];
int n;
int a[maxn],b[maxn];
int dp[maxn][maxn];

int dfs(int i,int j){
    if(i==0)return 0;
    int &res = dp[i][j];
    if(res==-1){
        res = dfs(i-1,j)+1;
        if(a[i]==b[j]) res = min(res,dfs(i-1,j-1));
        if(sa[n][b[j]]-sa[i][b[j]]>sb[n][b[j]]-sb[j][b[j]]) res = min(res,dfs(i,j-1));
    }
    return res;
}

int solve(){
    cin>>n;
    string tmp;
    cin>>tmp;
    for(int i=0;i<n;++i)a[i+1]=tmp[i]-'a';
    cin>>tmp;
    for(int i=0;i<n;++i)b[i+1]=tmp[i]-'a';
    for(int i=1;i<=n;++i){
        for(int j=0;j<26;++j){
            sa[i][j]=sa[i-1][j];
            sb[i][j]=sb[i-1][j];
        }
        sa[i][a[i]]++;
        sb[i][b[i]]++;
    }
    for(int i=0;i<26;++i)if(sa[n][i]!=sb[n][i])return -1;
    for(int i=0;i<=n;++i){
        for(int j=0;j<=n;++j){
            dp[i][j]=-1;
        }
    }
    int ans=dfs(n,n);
    if(ans==inf)ans=-1;
    return ans;
}

int main(){
    ios::sync_with_stdio(0);
    int T;
    cin>>T;
    while (T--) {
        cout<<solve()<<'\n';
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章