CF1363F_动态规划(最长公共子序列变种)

题目大意:

有两个长度为 n ( ≤ 2000 ) n(\le2000) n(2000)的字符串 s s s t t t,每次可以对s的一个子串 [ l , r ] [l,r] [l,r]中的字符进行顺时针移动,即: [ s l , s l + 1 , . . . , s r − 1 , s r ]   → [ s r , s l , s l + 1 , . . . , s r − 1 ] [s_l,s_{l+1},...,s_{r-1},s_{r}]\, \rightarrow [s_r,s_l,s_{l+1},...,s_{r-1}] [sl,sl+1,...,sr1,sr][sr,sl,sl+1,...,sr1],最少进行多少次操作可以将字符串 s s s变为 t t t,如果不行,则输出 − 1 -1 1

解题思路:

  • 首先判断是否可行,可行的充要条件是: s s s t t t中每种字符的个数都相同,因为每次对长度为2的子串进行操作,就相当于交换相邻的元素,那么这样一定可行

  • 题目的顺时针移动,其实就相当于选择一个元素,往前面的任意位置插入

  • 首先看个简单版本的问题:如果是选择一个元素往任意位置插入,最少操作次数是多少呢
    答 案 是 : 长 度 − 最 长 公 共 子 序 列 因 为 最 长 公 共 子 序 列 不 用 动 , 只 要 把 剩 下 的 字 符 往 对 应 位 置 插 入 即 可 答案是:长度-最长公共子序列 \\ 因为最长公共子序列不用动,只要把剩下的字符往对应位置插入即可

  • 那么这题就可以用类似的想法来实现,因为只能往前面插入,所以当 s [ i ] = t [ j ] s[i]=t[j] s[i]=t[j]时,还要判断 s [ i + 1 , . . . , n ] s[i+1,...,n] s[i+1,...,n]每种字符的个数都大于等于 t [ j + 1 , . . . , n ] t[j+1,...,n] t[j+1,...,n],才可以递推 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − 1 ] + 1 ) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1) dp[i][j]=max(dp[i][j],dp[i1][j1]+1)

可 以 反 证 来 证 明 : 假 设 存 在 某 种 字 符 x 小 于 的 话 因 为 只 能 往 前 插 字 符 , 所 以 s [ i + 1 , . . . , n ] 一 定 无 法 凑 出 足 够 个 数 的 x , 使 得 s [ i + 1 , . . . , n ] 中 x 的 个 数 等 于 t [ j + 1 , . . . , n ] 中 x 的 个 数 那 么 这 样 的 话 就 是 不 合 法 的 情 况 , 所 以 就 不 能 进 行 递 推 可以反证来证明:假设存在某种字符x小于的话 \\ 因为只能往前插字符,所以s[i+1,...,n]一定无法凑出足够个数的x,使得s[i+1,...,n]中x的个数等于t[j+1,...,n]中x的个数 \\ 那么这样的话就是不合法的情况,所以就不能进行递推 xs[i+1,...,n]x使s[i+1,...,n]xt[j+1,...,n]x

AC代码:

#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) //不能跟puts混用
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 2e3 + 10;
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const ll mod = 1e9 + 7;
int _, n;
int dp[maxn][maxn], suf_s[maxn][26], suf_t[maxn][26];
char s[maxn], t[maxn];
void init() {
    for (int i = 1; i <= n; i++) 
        for (int j = 0; j < 26; j++)
            suf_s[i][j] = suf_t[i][j] = 0;
    for (int i = 1; i <= n; i++) dp[0][i] = dp[i][0] = 0;
}
int main() {
    IOS;
    cin >> _;
    while (_--) {
        cin >> n >> s + 1 >> t + 1;
        init();
        for (int i = n; i >= 1; i--)
            for (int j = 0; j < 26; j++)
                suf_s[i][j] = suf_s[i + 1][j] + (s[i] - 'a' == j);
        for (int i = n; i >= 1; i--)
            for (int j = 0; j < 26; j++)
                suf_t[i][j] = suf_t[i + 1][j] + (t[i] - 'a' == j);
        bool ok = true;
        for (int i = 0; i < 26; i++) if (suf_t[1][i] != suf_s[1][i]) ok = false;
        if (!ok) cout << -1 << endl;
        else {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                    if (s[i] == t[j]) {
                        bool ok = true;
                        for (int k = 0; k < 26; k++) if (suf_s[i][k] < suf_t[j][k]) ok = false;
                        if (ok) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
                    }
                }
            }
            cout << n - dp[n][n] << endl;
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值