题意:你被给予了两个字符串s和t,每个字符串的长度都是n并且是小写字母,你的目标是让s变成t。
你可以进行如下的操作多次,使得字符串s变成字符串t,选择字符串s的子串并使得它旋转,即让(s[l, l + 1...r])变成字符串(s[r, l, l + 1...r - 1]),其它字符保持原有的位置。求最少的操作次数让字符串s变成字符串t,并确定是否它可以。
分析:每一个操作可以让一个字符提到前面的任何位置,并不能提到到后面,最少的操作次数是n - 两个字符串的最长公共子序列,但是这个最长公共子序列是受到限制的,受到什么限制呢?就是两个字符串匹配的时候,比如s的字符i和t的字符j匹配的时候,那么s的字符i后面的每种类型的字符数量都要大于t的j字符后面的每种类型的字符数量,这样s后面不匹配的字符可以提到前面来,我们求最长公共上升子序列的时候,我们的dp方程(dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1))不仅要满足s[i] == t[j],并且满足每种字符的后缀和suf_s[i][alpha]都大于等于suf_t[i][alpha]。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n;
scanf("%d", &n);
string s, t;
cin >> s >> t;
string tmps = s, tmpt = t;
sort(tmps.begin(), tmps.end()), sort(tmpt.begin(), tmpt.end());
if (tmps != tmpt)
{
puts("-1");
continue;
}
//偏移一个位置
s = "?" + s;
t = "?" + t;
//dp数组
vector<vector<int>> dp(n + 1, vector<int>(n + 1));
//统计后缀中每个字符的数量
vector<vector<int>> suf_s(n + 2, vector<int>(26)), suf_t(n + 2, vector<int>(26));
for (int i = n; i >= 1; --i)
{
for (int j = 0; j < 26; ++j)
{
suf_s[i][j] = suf_s[i + 1][j];
suf_t[i][j] = suf_t[i + 1][j];
}
++suf_s[i][s[i] - 'a'];
++suf_t[i][t[i] - 'a'];
}
//求满足限制的最长公共子序列
//初始化
dp[0][0] = 0;
for(int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
dp[i][j] = max(dp[i][j], max(dp[i - 1][j], dp[i][j - 1]));
if (s[i] == t[j])
{
//必须满足限制,才能转移
bool flag = true;
for (int k = 0; k < 26; ++k)
{
if (suf_s[i][k] < suf_t[j][k])
flag = false;
}
if (flag)
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
}
}
printf("%d
", n - dp[n][n]);
}
return 0;
}