CF771D Bear and Company 题解 动态规划

题目链接:https://codeforces.com/problemset/problem/771/D

题目大意:给你一个长度为 \(n(1 \le n \le 75)\) 的字符串,每一步操作你可以交换字符串中相邻的两个元素。求最少的操作步数,使得字符串中不包含子串 “VK”。

解题思路:

因为除了 'V' 和 'K' 以外的所有字符的效果都是一样的,所以我们可以把其它的字符都视为 'X'。

我们会从左往右一步步构造出最终的字符串。令 \(dp[v][k][x]\) 表示构造一个包含 \(v\) 个字符 'V',\(k\) 个字符 'K' 以及 \(x\) 个字符 'X' 的字符串前缀所需要的操作次数(这个字符串前缀对应的长度为 \(v+k+v\))。但是这个状态是不完整的,我们还需要开一个维度来确保 'V' 和 'K' 有没有连接到一起(确保不存在 'V' 后面紧跟着一个 'K' 的情况出现)。所以我们需要再开一个维度来确保这个长度为 \(v+k+x\) 的字符串前缀的最后一个字符是不是 'V'。于是,最终定义的状态为 \(dp[v][k][x][lastLetter]\)(或者可以表述成 \(dp[v][k][x][is\_the\_last\_letter\_V]\)\(\Rightarrow\) 它表示构造一个包含 \(v\) 个字符 'V'、\(k\) 个字符 'K'、\(x\) 个字符 'X' 且最后一个字符是不是 'V' 的字符串前缀需要的最少交换次数。

要从当前状态转移到下一个状态,我们需要考虑下一个 'K'(也就是初始状态下的第 \(k+1\) 个字符 'K')的位置,下一个 'V' 个位置,以及下一个 'X' 的位置。当然,如果当前状态的最后一个字符是 'V',我们的下一个状态的最后一个字符不能是 'K'。

也就是说,如果当前状态是 \(dp[v][k][x][i]\) (当前有字符串前缀有 \(v\) 个字符 'V',\(k\) 个字符 \(K\)\(x\) 个字符 'X',\(i=1\) 表示前缀的最后一个字符是 'V',\(i=0\) 表示前缀的最后一个字符不是 'V')的话,则它可以扩展出如下状态(假设字符 'V' 的总数为 \(V\),字符 'K' 的总数为 \(K\),其他字符(视为 'X')的总数为 \(X\)):

  • 如果 \(v \lt V\),则可以扩展到状态 \(dp[v+1][k][x][1]\)(将第 \(v+1\) 个 'V' 移动到第 \(v+k+x+1\) 个位置);
  • 如果 \(k \lt K\),则可以扩展到状态 \(dp[v][k+1][x][0]\)(将第 \(k+1\) 个 'K' 移动到第 \(v+k+x+1\) 个位置);
  • 如果 \(x \lt X\),则可以扩展到状态 \(dp[v][k][x+1][0]\)(将第 \(x+1\) 个 'X' 移动到第 \(v+k+x+1\) 个位置)。

那么将一个字符转移到第 \(v+k+x+1\) 个位置需要交换几次呢?

可以发现,之前的状态是 \(dp[v][k][x][i]\),所以前 \(v\) 个 'V'、前 \(k\) 个 'K'、前 \(x\) 个 'X' 的位置我们不用管,假设要转移的字符一开始在第 \(p\) 个位置,则它对应的交换次数是:

\(v+1\) 个 'V' 开始所有初始位置 \(\lt p\) 的字符 'V' 个数 + 第 \(k+1\) 个 'K' 开始所有初始位置 \(\lt p\) 的字符 'K' 个数 + 第 \(x+1\) 个 'X' 开始所有初始位置 \(\lt p\) 的字符个数 之和。

这就是转移的代价。

计算转移的代价可以有更快地解法,不过我们就算以 \(O(n)\) 的算法解决也可以过,那么遍历 'V'、'K'、'X' 以及计算转移的代价,总时间复杂度 \(O(n^4)\) 可以解决这个问题。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 77;
int n, dp[maxn][maxn][maxn][2], V, K, X;
char s[maxn];
vector<int> vec_v, vec_k, vec_x;

int cal_swap_time(int v, int k, int x, int p) {
    int cnt = 0;
    for (int i = v; i < V && vec_v[i] < p; i ++) cnt ++;
    for (int i = k; i < K && vec_k[i] < p; i ++) cnt ++;
    for (int i = x; i < X && vec_x[i] < p; i ++) cnt ++;
    return cnt;
}

void chkmin(int &a, int b) {
    a = min(a, b);
}

int main() {
    scanf("%d%s", &n, s+1);
    for (int i = 1; i <= n; i ++) {
        if (s[i] == 'V') vec_v.push_back(i);
        else if (s[i] == 'K') vec_k.push_back(i);
        else vec_x.push_back(i);
    }
    V = vec_v.size();
    K = vec_k.size();
    X = vec_x.size();
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0][0][0] = 0;
    for (int v = 0; v <= V; v ++) {
        for (int k = 0; k <= K; k ++) {
            for (int x = 0; x <= X; x ++) {
                for (int i = 0; i < 2; i ++) {
                    int tmp = dp[v][k][x][i];
                    if (v < V) chkmin(dp[v+1][k][x][1], tmp + cal_swap_time(v, k, x, vec_v[v]));
                    if (k < K && i==0) chkmin(dp[v][k+1][x][0], tmp + cal_swap_time(v, k, x, vec_k[k]));
                    if (x < X) chkmin(dp[v][k][x+1][0], tmp + cal_swap_time(v, k, x, vec_x[x]));
                }
            }
        }
    }
    printf("%d\n", min(dp[V][K][X][0], dp[V][K][X][1]));
    return 0;
}
posted @ 2020-10-28 19:32  quanjun  阅读(96)  评论(0编辑  收藏  举报