Loading

[abc265F] Erase Subarrays 题解

空心蓝好简单。为什么我还是打不上蓝呢。

为什么我次次 debug 40+min 呢 /ll


题意:给定数组 \(a\),对每个 \(i \in [1,m]\) 求最少做几次操作让剩余元素和为 \(i\)

一次操作定义为:取出一个连续子区间并删去。


首先区间不会重叠。如果重叠了可以只用一次删掉。

然后这很一眼 dp。我们来想想为了得到答案,什么是后续转移中需要关心的。

嗯留下来的数的总和需要知道。这一维是 \(O(m)\) 的,存的下。

接着是段数怎么用最少的信息转移。把贡献放到被删除的第一个数上,我们只需要知道一个数和其上个数状态是否相同就能转移。那么再多 \(O(1)\) 大小的一维表示是否被选的状态。

一个数只有选或不选,所以单个状态的转移是 \(O(1)\) 的。

所以就结束了。空间复杂度 \(O(nm)\),时间复杂度 \(O(nm)\),当然你可以用滚动数组把空间做到 \(O(m)\),但显然没必要。

这才能叫给 "beginner" 的 F 罢。

代码很好写也很好调。我的代码中 \(1\) 是这个数不删除,\(0\) 是被删除。

dp 题的重点应该是状态的设计。只要想着什么会影响答案,或者说,怎么用最少的信息量提供能用于转移的信息,就够了。

还有一个思考方向是什么样的情况可以一起转移。感觉这种经典线性 dp 小拓展很板啊。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int M = 3005;
int dp[M][M][2]; int n, a[M], m;
int main(){
    scanf("%d %d", &n, &m);
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0][1] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        for(int j = 0; j <= m; j++) {
            dp[i][j][0] = min(dp[i][j][0], dp[i-1][j][1] + 1);
            dp[i][j][0] = min(dp[i][j][0], dp[i-1][j][0]);
        }
        for(int j = 0; j <= m-a[i]; j++) {
            dp[i][j+a[i]][1] = min(dp[i][j+a[i]][1], dp[i-1][j][1]);
            dp[i][j+a[i]][1] = min(dp[i][j+a[i]][1], dp[i-1][j][0]);
        }
    }
    for(int i = 1; i <= m; i++) {
        int ans = min(dp[n][i][0], dp[n][i][1]);
        printf("%d\n", ans == 0x3f3f3f3f ? -1 : ans);
    }
}
posted @ 2022-11-01 19:53  purplevine  阅读(52)  评论(0)    收藏  举报