返回

动态规划巧解 P3736 字符串合并,开启算法新篇章!

前端

在浩瀚的算法天地里,动态规划作为一盏明灯,照亮了无数算法难题的解决之路。今天,让我们跟随它的指引,开启一段精彩的探索之旅,破解洛谷 P3736 [HAOI2016]字符合并的奥秘,在这个过程中,我们不仅将掌握动态规划的精髓,更将领略算法思维的独特魅力。

洛谷 P3736 题目给定长度为 n 的 01 字符串,每次选取连续长度为 k 的子串进行合并,将合并后的结果按字典序从小到大依次输出。

输入格式:

n k
s

其中,n 表示字符串长度,k 表示子串长度,s 表示给定的 01 字符串。

输出格式:

按字典序从小到大依次输出所有合并后的结果。

算法剖析:动态规划的巧妙应用

本题看似简单,但想要找到所有合并后的结果,却需要算法的巧妙应用。动态规划算法正是为这种类型的题目而生,它通过将大问题分解为一系列子问题,逐个解决,最终汇聚成问题的整体解法。

在 P3736 中,我们定义 dp[i][j][0/1] 表示考虑前 i 个字符,已经合并 j 个子串,当前子串末尾字符为 0/1 的最优解。其中,dp[i][j][0] 表示前 i 个字符,已经合并 j 个子串,当前子串末尾字符为 0 的最优解;dp[i][j][1] 表示前 i 个字符,已经合并 j 个子串,当前子串末尾字符为 1 的最优解。

接下来,我们逐步推导出 dp 数组的转移方程:

  • dp[i][j][0] = min(dp[i-1][j][0], dp[i-1][j][1] + (s[i] == '1'))
  • dp[i][j][1] = min(dp[i-1][j][1], dp[i-1][j][0] + (s[i] == '0'))

其中,dp[i][j][0] 表示前 i 个字符,已经合并 j 个子串,当前子串末尾字符为 0 的最优解;dp[i][j][1] 表示前 i 个字符,已经合并 j 个子串,当前子串末尾字符为 1 的最优解。

通过上述转移方程,我们可以逐步求解出 dp 数组中的所有元素。最后,我们只需要从 dp[n][k][0] 和 dp[n][k][1] 中选取较小的值,即可得到题目要求的最优解。

代码实现:算法思想的编程呈现

#include <iostream>
#include <cstring>
using namespace std;

const int N = 510, M = 110;
int n, m, f[N][M][2];
char s[N];

int main() {
    cin >> n >> m >> s;
    memset(f, 0x3f, sizeof f);
    f[0][0][0] = f[0][0][1] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            for (int k = 0; k < 2; k++) {
                f[i][j][k] = f[i - 1][j][k];
                if (k == 0 && j > 0) f[i][j][k] = min(f[i][j][k], f[i - 1][j - 1][1] + (s[i] == '1'));
                if (k == 1 && j > 0) f[i][j][k] = min(f[i][j][k], f[i - 1][j - 1][0] + (s[i] == '0'));
            }
        }
    }
    cout << min(f[n][m][0], f[n][m][1]) << endl;
    return 0;
}