CF1433F 矩阵选数
1 CF1433F 矩阵选数
2 题目描述
时间限制 \(1s\) | 空间限制 \(256M\)
给出一个大小为 \(n \times m\) 的整数矩阵 \(a\)。每一行可以选择不超过 \(\left\lfloor\frac{m}{2}\right\rfloor\) 个元素。你的任务是选择一些元素使得他们可以被 \(k\) 整除,并且他们的累加和最大。换句话说每一行你可以选择不超过一半的的元素,你要找出能被 \(k\) 整除的最大累加和。注意你可以选择零个元素,当然累加和就是零。
数据范围:\(1≤𝑛,𝑚,𝑘≤70\)
3 题解
我们发现,这道题中的约束条件大概是这两条:每行选取的数的个数不能超过 \(\dfrac{m}{2}\),总价格必须为 \(k\) 的倍数。由于数据范围出奇的小,我们可以考虑一个包含这两个约束条件的 \(dp\)。首先,我们肯定需要有前两维:分别表示当前考虑了前几行和当前行考虑了前几个数。其次,我们需要把这两个约束条件包括进去。第一个约束条件简单,只需要再加一维记录选取的个数即可。但是第二个约束条件就有些复杂了。在看到 \(k \le 70\) 这一奇怪的条件后,我们想到:可以在最后一维存储当前总价格对 \(k\) 取余所得结果。于是状态就明了了:\(dp_{i, j, x, l}\) 表示考虑前 \(i\) 行,第 \(i\) 行考虑前 \(j\) 个,当前行选取了 \(x\) 个,当前和对 \(k\) 取模的结果为 \(l\) 时所能得到的最大值。设计完状态,转移也就出来了:
注意这里我们将 \(Map_{i, j}\) 先对 \(k\) 取了一次模,保证取模后的结果小于 \(k\),然后在减完当前数后加上 \(k\) 再模 \(k\),保证了全过程不会出现大于等于 \(k\) 的或者是负数的下标出现。
这里我们在每一行开头的地方需要从上一行的最后一个数转移过来,以衔接上之前所算出的值。
4 代码(空格警告):
#include <iostream>
#include <cstring>
using namespace std;
const int N = 71;
int n, m, k, maxn;
int Map[N][N];
int dp[N][N][N][N];
int main()
{
memset(dp, -0x3f, sizeof(dp));
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> Map[i][j];
for (int i = 1; i <= n; i++)
{
dp[i][0][0][0] = 0;
for (int j = 0; j <= m/2; j++)
{
for (int l = 0; l < k; l++)
{
dp[i][0][0][l] = max(dp[i][0][0][l], dp[i-1][m][j][l]);
}
}
for (int j = 1; j <= m; j++)
{
for (int x = 0; x <= m/2; x++)
{
for (int l = 0; l < k; l++)
{
if (x == 0) dp[i][j][x][l] = dp[i][j-1][x][l];
else
{
dp[i][j][x][l] = max(dp[i][j-1][x][l], dp[i][j-1][x-1][(l-(Map[i][j] % k) + k) % k] + Map[i][j]);
}
}
}
}
}
for (int i = 0; i <= m/2; i++) maxn = max(maxn, dp[n][m][i][0]);
cout << maxn;
return 0;
}