Rotating Substrings
題意
給定兩個等長的字符串 (長度至多2000),對第字符串 可以做如下操作:將一個字串向右旋轉一格,問最少幾次操作將其變成 字符串。
觀察
旋轉操作過於醜陋,事實上觀察容易發現,對區間 旋轉,等價於將 移到 前面,此外沒有影響。
分析
先考慮可行性,顯然如果兩個字符串各個字符數量不一致則無解,反之一定有解。
再分析如何求解最優解。事實上旋轉操作可以視爲將一個元素挑出來,移到前面的任何位置。可以發現,元素只能向前移,不能向後移,所以後面的元素一定先於前面的元素確定,考慮從後向前推。
不失一般性,考慮特定的兩位 , 假設其後的內容已經被確定,考慮完成之前的內容的匹配需要的次數,記爲 ,那麼有如下三種情況
- :即花費 的代價將 移動到之前某個位置
- :即兩者末位一樣,可以一起消掉
- :其中 表示字符串 中 字符 個數到 爲止的前綴和。 這裏的條件即是要求 的 區間內字符 對應的字符的個數多於 的 區間內 對應的字符的個數。這個操作的物理意義是將 中後面的某個 字符移動到當前位置,並且和 向抵消。這步移動的操作對應之前某次進行的情況1,且代價在那時已經進行過計算。
- :當字符串 已經被全部pick過或者消去過時,那麼在滿足有解條件的情況下,一定可以匹配出對應的答案,且所消耗的操作次數之前已經計算過,所以結果就是0。
於是根據上述條件容易得到dp遞推式和邊界條件,且情況4已經囊括了所有的邊界條件,因爲 在遞歸過程中恆成立,唯一使 減小的情況3發生 的必要條件之一是 。且 的物理意義也容易得到: 字符串到 爲止在塞入後面某些移過來的元素後能夠與 字符串到 爲止完全匹配的最少操作次數。
代碼
得到dp式子以後容易編碼,可以用記憶化搜索實現。事先對前綴和做預處理即可。
總共狀態數是 ,每個狀態轉移需要 時間,所以總複雜度是 。
#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';
}
}