CF946D Timetable(动态规划)

Timetable

题意:

给出 \(n\)\(m\)\(01\) 串,对于每一行所要花费的代价是行中第一个 \(1\) 和最后一个 \(1\) 之间的距离加一,现在你有魔法可以去除掉 \(k\)\(1\),问去掉不多于 \(k\)\(1\) 的情况下,你所能获得的最小代价是多少。

思路:

看到有 \(k\) 次限制求最小代价,不难想到需要用到 \(DP\) 来解决,不妨考虑设状态 \(dp[i][j]\) 表示在前 \(i\) 个串中逃了 \(j\) 次课最少的上课时间是多少。那么就可以将逃了多少次课视作一个物品的容量,每一天上课的时间看作是物品的价值,这样就转化成了一个背包问题。但是每一天到底哪些课要逃,哪些课不逃不太好处理,所以需要对每一天做一个预处理。
定义一个数组 \(v[i][j]\) 表示第 \(i\) 天翘了 \(j\) 节课的最小代价,因为题目说了,一天的在校时间是最后一节课的时间减去第一节课的时间,所以只有逃首尾两端的课才可以让贡献变小,那么只需要去 \(O\left(n ^ 2\right)\) 的循环计算一下中间要上哪些课就可以了

    memset(v, 0x3f, sizeof v);
    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        int len = 0;
        for (int j = 0; j < m; j++) {
            if (s[j] & 1) a[++len] = j + 1;  //记录哪些时间段有课
        }
        
        v[i][len] = 0;
        siz[i] = len;
        for (int j = 1; j <= len; j++) 
            for (int k = j; k <= len; k++) 
                v[i][len - (k - j + 1)] = min(v[i][len - (k - j + 1)], a[k] - a[j] + 1);  //中间要上课的时间
    }

之后就是一个基础的背包转移

    memset(dp, 0x3f, sizeof dp);
    for (int i = 0; i <= p; i++) dp[0][i] = 0;
 
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= p; j++) {
            for (int k = 0; k <= min(j, siz[i]); k++) {
                dp[i][j] = min(dp[i][j], dp[i - 1][j - k] + v[i][k]);
            }
        }
    }
 
    printf("%d\n", dp[n][p]);
posted @ 2023-01-17 23:04  浅渊  阅读(58)  评论(0)    收藏  举报