题目链接:CF946D Timetable
这道题很显然是一道 DP 题,下面是我的思路。
:第 i 行从最左边的 1 开始删去 j 个 1 后下一个 1 的位置。
:第 i 行从最右边的 1 开始删去 j 个 1 后下一个 1 的位置。
:第 i 行有几个 1 。
:第 i 行删去 j 个 1 剩下的最小时间。
这上面的数组中,s 数组是我们 DP 是要用的,所以我们考虑如何用 pre 和 suf 推出 s。
pre 和 suf 都可以 O(nm) 的推出来,首先考虑 s 的特殊值。
这些都很好理解,因为 n 和 m 都只有 500 , 所以我们可以枚举前后各删了几个 1 最后去最小,及:
就这样,我们可以 的推出 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 的个数,对于以下几种情况:
-
sum 为 0, 直接输出 0 即可。
-
, 直接输出 0,因为可以把 1 全删了。
-
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 部分我认为反而还没有那么难想,我们设 为前 i 行删 j 个 1 后最小的代价,我们可以枚举第 i 行删了多少个,这样就可以得出下面的式子:
然后答案就是 ,而 DP数组还可以用滚动数组优化,dp 为当前的数组,
为上一次的。代码如下:
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;
}