题目链接:HDU-6549 String
题意
wls 有一个长度为 $n$ 的字符串,每次他可以将一个长度不大于 $L$ 的子串修改成同一种字母,问至少修改多少次可以使字符串最多含有 $k$ 段。连续的只含同一种字母的子串被称为一段,比如说 aaabbccaaa 共含有 4 段。
($1leq n, L leq 10^5, 1leq k leq 10$)
思路
定义 dp[i][j][p],表示第 i 位为 j+'a' 时 [0, i] 至多 p 段的最小修改次数。
如果 str[i] == j+'a',第 i 位不用修改,那么dp值直接从 i-1 转移过来:
$$
dp[i][j][p] = min(dp[i-1][j][p], dp[i-1][c][p-1]),(cin [0,25],c
ot= j)
$$
否则第 i 位必须要修改一次,因为最后要求的是最少修改多少次使得字符串最多含有 k 段,根据贪心策略,尽量修改更多,使前面更多位与第 i 位为同一段,那么dp值从 max(i-L, 0) 转移过来:
$$
dp[i][j][p] = min(dp[q][j][p], dp[q][c][p-1])+1,(q = max(0, i-L),cin [0,25], c
ot= j)
$$
这样时间复杂度是 $O(26^2nk)$,可以通过前后缀优化,优化掉 $c$ 的遍历。定义 $pre[i][j][p]$ 表示 $dp[i][0sim j][p]$ 的最小值,$suf[i][j][p]$ 表示 $dp[i][jsim 25][p]$ 的最小值,则
当 str[i] == j+'a' 时:
$$
dp[i][j][p] = min(dp[i-1][j][p], pre[i-1][j-1][p-1], suf[i-1][j+1][p-1])
$$
当 str[i] != j+'a' 时:
$$
dp[i][j][p] = min(dp[q][j][p], pre[q][j-1][p-1], suf[q][j+1][p-1])+1,(q=max(0,i-L))
$$
这样时间复杂度是 $O(26nk)$。
边界的转移要注意一下。
代码实现
#include <iostream> #include <cstdio> #include <cstring> using std::max; using std::min; const int INF = 0x3f3f3f3f; const int N = 100010; // dp[i][j][p]表示第i位为j+'a'时[0,i]至多q段的最小修改次数 // pre[i][j][p]表示dp[i][0~j][p]的最小值 // suf[i][j][p]表示dp[i][j~25][p]的最小值 int dp[N][27][11], pre[N][27][11], suf[N][27][11]; char str[N]; void init() { for (int i = 0; i < N; i++) { for (int j = 0; j < 26; j++) { dp[i][j][0] = INF; pre[i][j][0] = INF; suf[i][j][0] = INF; } } for (int i = 0; i < 26; i++) { if (str[0] == i + 'a') dp[0][i][1] = 0; else dp[0][i][1] = 1; if (i) pre[0][i][1] = min(pre[0][i-1][1], dp[0][i][1]); else pre[0][i][1] = dp[0][i][1]; } suf[0][25][1] = dp[0][25][1]; for (int i = 24; i >= 0; i--) suf[0][i][1] = min(suf[0][i+1][1], dp[0][i][1]); } int main() { int n, l, k; while (~scanf("%d %d %d %s", &n, &l, &k, str)) { init(); for (int i = 1; i < n; i++) { for (int j = 0; j < 26; j++) { for (int p = 1; p <= k; p++) { if (str[i] == 'a' + j) { dp[i][j][p] = min(dp[i-1][j][p], min((j ? pre[i-1][j-1][p-1] : INF), (j == 25 ? INF : suf[i-1][j+1][p-1]))); } else { int q = max(0, i - l); dp[i][j][p] = min(dp[q][j][p], min((j ? pre[q][j-1][p-1] : INF), (j == 25 ? INF : suf[q][j+1][p-1]))) + 1; } if (j) pre[i][j][p] = min(pre[i][j-1][p], dp[i][j][p]); else pre[i][j][p] = dp[i][j][p]; } } for (int j = 25; j >= 0; j--) { for (int p = 1; p <= k; p++) { if (j < 25) suf[i][j][p] = min(suf[i][j+1][p], dp[i][j][p]); else suf[i][j][p] = dp[i][j][p]; } } } printf("%d ", pre[n-1][25][k]); } return 0; }