zoukankan      html  css  js  c++  java
  • 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
    ", min(dp[V][K][X][0], dp[V][K][X][1]));
        return 0;
    }
    
  • 相关阅读:
    .NET Task揭秘(一)
    .net线程池内幕
    Branch 向量化
    让你的gdb print 更可读
    获取web项目的绝对路径的方法总结
    Android事件监听(一)——简介篇
    Android事件监听(二)——点击鼠标事件
    jsp运行环境的安装和配置
    log4j中的DailyRollingFileAppender日志输出器工作原理
    开发环境搭建
  • 原文地址:https://www.cnblogs.com/quanjun/p/13892931.html
Copyright © 2011-2022 走看看