HDU-6549 String (前后缀优化dp)

题目链接:HDU-6549 String

题意

wls 有一个长度为 $n$ 的字符串,每次他可以将一个长度不大于 $L$ 的子串修改成同一种字母,问至少修改多少次可以使字符串最多含有 $k$ 段。连续的只含同一种字母的子串被称为一段,比如说 aaabbccaaa 共含有 4 段。

($1\leq n, L \leq 10^5, 1\leq 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]),(c\in [0,25],c\not= 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),c\in [0,25], c\not= j)
$$

这样时间复杂度是 $O(26^2nk)$,可以通过前后缀优化,优化掉 $c$ 的遍历。定义 $pre[i][j][p]$ 表示 $dp[i][0\sim j][p]$ 的最小值,$suf[i][j][p]$ 表示 $dp[i][j\sim 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\n", pre[n-1][25][k]);
    }
    return 0;
}
View Code

 

posted @ 2019-10-09 15:24  _kangkang  阅读(514)  评论(0编辑  收藏  举报