CF946D Timetable 题解

题目链接:CF946D Timetable

这道题很显然是一道 DP 题,下面是我的思路。

pre_{i,j}:第 i 行从最左边的 1 开始删去 j 个 1 后下一个 1 的位置。

suf_{i,j}:第 i 行从最右边的 1 开始删去 j 个 1 后下一个 1 的位置。

cnt_{i}​:第 i 行有几个 1 。

s_{i, j}:第 i 行删去 j 个 1 剩下的最小时间。

这上面的数组中,s 数组是我们 DP 是要用的,所以我们考虑如何用 pre 和 suf 推出 s。

pre 和 suf 都可以 O(nm) 的推出来,首先考虑 s 的特殊值。

                                                ​​​​​​​     s_i,_0=suf_i,_0-pre_i,_0+1

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​             ​​s_i,cnti-1 = 1

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​       s_i,cnt i=0

这些都很好理解,因为 n 和 m 都只有 500 , 所以我们可以枚举前后各删了几个 1 最后去最小,及:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​   s_i,_j=min[suf_i,_l-pre_i,_j-_l+1]

就这样,我们可以 O(nm^{2}) 的推出 s, 下面是这部分代码:

void prepare() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            cnt[i] += a[i][j];
        int x = 0;
        for (int j = 1; j <= m; j++)
            if (a[i][j] == 1)
                pre[i][x++] = j;
        x = 0;
        for (int j = m; j >= 1; j--)
            if (a[i][j] == 1)
                suf[i][x++] = j;
        for (int j = 1; j <= cnt[i]; j++)
            s[i][j] = 2e9;
        s[i][0] = suf[i][0] - pre[i][0] + 1;
        s[i][cnt[i]] = 0;
        s[i][cnt[i] - 1] = 1;
        for (int j = 1; j <= cnt[i] - 2; j++) 
            for (int l = 0; l <= j; l++)
                s[i][j] = min(s[i][j], suf[i][l] - pre[i][j - l] + 1);
    }
}

对于接下来的 DP ,我们其实可以先处理一些特殊值,因为原题数据十分苛刻,所以我的代码对于一些特殊情况处理不是那么到位,只能特判一下。设 sum 为总共 1 的个数,对于以下几种情况:

  1. sum 为 0, 直接输出 0 即可。

  2. sum\leq k, 直接输出 0,因为可以把 1 全删了。

  3. sum == k - 1, 直接输出 1, 因为可以把 1 删到只剩 1 个。

这部分代码我包装成一个函数 chk(),如果特判成立,返回 true, 否则返回false 。

bool chk() {
    int sum = 0;
    for (int i = 1; i <= n; i++)
        sum += cnt[i];
    if (sum == 0)
        cout << 0 << endl;
    else if (sum <= k)
        cout << 0 << endl;
    else if (sum == k - 1)
        cout << 1 << endl;
    else    
        return false;
    return true;
}

这道题的 DPDP 部分我认为反而还没有那么难想,我们设 dp_{i,j} 为前 i 行删 j 个 1 后最小的代价,我们可以枚举第 i 行删了多少个,这样就可以得出下面的式子:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​     dp_{i,j}= min[dp_{i-1,j-l+s i,l}]

然后答案就是 dp_{n, k} ,而 DP数组还可以用滚动数组优化,dp 为当前的数组, dp_2 为上一次的。代码如下:

void solve() {
    if (chk())
        return;
    memset(dp, 0x3f3f3f3f, sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i++) {
        memcpy(dp2, dp, sizeof dp2);
        dp[0] += s[i][0];
        for (int j = 1; j <= k; j++) {
            dp[j] = dp2[j] + s[i][0];
            for (int l = 1; l <= j && l <= cnt[i]; l++) 
                dp[j] = min(dp[j], dp2[j - l] + s[i][l]);
        }
    }
    cout << (dp[k] == 0x3f3f3f3f ? 0 : dp[k]) << endl;
}

这样就可以AC了。

完整代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m, k;
int a[505][505] = {{0}};
int s[505][505] = {{0}}; 
int pre[505][505] = {{0}}, suf[505][505] = {{0}};
int cnt[505] = {{0}};
void init() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%1d", &a[i][j]);
}
void prepare() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            cnt[i] += a[i][j];
        int x = 0;
        for (int j = 1; j <= m; j++)
            if (a[i][j] == 1)
                pre[i][x++] = j;
        x = 0;
        for (int j = m; j >= 1; j--)
            if (a[i][j] == 1)
                suf[i][x++] = j;
        for (int j = 1; j <= cnt[i]; j++)
            s[i][j] = 2e9;
        s[i][0] = suf[i][0] - pre[i][0] + 1;
        s[i][cnt[i]] = 0;
        s[i][cnt[i] - 1] = 1;
        for (int j = 1; j <= cnt[i] - 2; j++) 
            for (int l = 0; l <= j; l++)
                s[i][j] = min(s[i][j], suf[i][l] - pre[i][j - l] + 1);
    }
}
int dp[505] = {0}, dp2[505] = {0};
bool chk() {
    int sum = 0;
    for (int i = 1; i <= n; i++)
        sum += cnt[i];
    if (sum == 0)
        cout << 0 << endl;
    else if (sum <= k)
        cout << 0 << endl;
    else if (sum == k - 1)
        cout << 1 << endl;
    else    
        return false;
    return true;
}
void solve() {
    if (chk())
        return;
    memset(dp, 0x3f3f3f3f, sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i++) {
        memcpy(dp2, dp, sizeof dp2);
        dp[0] += s[i][0];
        for (int j = 1; j <= k; j++) {
            dp[j] = dp2[j] + s[i][0];
            for (int l = 1; l <= j && l <= cnt[i]; l++) 
                dp[j] = min(dp[j], dp2[j - l] + s[i][l]);
        }
    }
    cout << (dp[k] == 0x3f3f3f3f ? 0 : dp[k]) << endl;
}
int main() {
    init();
    prepare();
    solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值