写在前面

思维难度不是很大的 DP,代码实现也很容易。

状态设计模式很套路,转移也很好理解。

算法思路

(因为 \(k\) 是常用的循环变量,下文中将题面中的模数改为 \(p\))

虽然要求的是模 \(p\) 结果为 \(0\) 的答案,显然这个结果受到选择的数的和可能受到模 \(p\) 任何余数的影响。因此不妨设计状态的时候想到用一维来表示答案模 \(p\) 的余数。

加上这一维之后就是一个背包模板了。

设 \(f_{i, j, t, k}\) 表示在第 \(i\) 行前 \(j\) 个数中选出 \(t\) 个数,且模 \(p\) 余 \(k\) 的最大答案。

那么显然有:

\[f_{i, j, t, k} = \max\{f_{i, j - 1, t, k}, f_{i, j - 1, t - 1, (k - a_{i, j})\bmod p} + a_{i, j}\}
\]

当可能存在多个(或多组) \(a_{i, j}\) 可能将同一个答案更新的时候,取最大值即可。

这样分别处理处每一行的答案之后,再设 \(g_{i, j}\) 表示在前 \(i\) 行中选择某些数使得其和模 \(p\) 的余数为 \(j\) 的最大答案。

那么显然有:

\[g_{i, j} = \max\{g_{i - 1, (j - k)\bmod p} + f_{i, m, t, k}\}
\]

Tips

  • 注意一些边界条件。

  • 处理的时候可能会出现不合法的状态,即在某一行内选出某些数,其和模 \(p\) 的余数根本不可能等于枚举的 \(k\) 的情况。这种处理方式归根到底还是因为它是从“选 \(0\) 个数的时候余数却不为 \(0\)”这个不合法状态转移来的。有个简单的处理方式,只需要将初值设为负无穷,然后将合法的初始状态(即选择数的个数和余数都为 \(0\))设为 \(0\) 就好了。

Code

  • 代码中对于每一行分别处理了这一行的 \(f\) 数组,并紧接着更新 \(g\) 数组,因此将 \(f\) 数组删掉了一维。

  • 代码中还使用了滚动数组。

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 75;

namespace Read {
inline int read() {
int fh = 1, res = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') fh = -1;
for(; isdigit(ch); ch = getchar()) res = (res << 3) + (res << 1) + (ch ^ '0');
return fh * res;
}
}
using namespace Read; int n, m, p; int a[Maxn][Maxn]; int f[2][Maxn][Maxn];
int g[2][Maxn]; int main() {
n = read(); m = read(); p = read();
for(register int i = 1; i <= n; ++i) {
for(register int j = 1; j <= m; ++j) {
a[i][j] = read();
}
}
memset(g, -0x3f, sizeof(g));
g[0][0] = 0;
for(register int i = 1; i <= n; ++i) {
memset(f, -0x3f, sizeof(f));
f[0][0][0] = 0;
for(register int j = 1; j <= m; ++j) {
f[j & 1][0][0] = 0;
for(register int t = 1; t <= min(j, m/2); ++t) {
for(register int k = 0; k < p; ++k) {
f[j & 1][t][k] = max(max(f[j & 1][t][k], f[j - 1 & 1][t][k]), f[j - 1 & 1][t - 1][(k - (a[i][j] % p) + p) % p] + a[i][j]);
}
}
}
for(register int j = 0; j < p; ++j) {
g[i & 1][j] = g[i - 1 & 1][j];
for(register int t = 0; t <= (m / 2); ++t) {
for(register int k = 0; k < p; ++k) {
g[i & 1][j] = max(g[i & 1][j], g[i - 1 & 1][(j - (f[m & 1][t][k] % p) + p) % p] + f[m & 1][t][k]);
}
}
}
}
printf("%d", g[n & 1][0]);
return 0;
}

最新文章

  1. 《C和指针(Pointer on c)》 学习笔记(转自:http://dsqiu.iteye.com/blog/1687944)
  2. Save a bricked Samsung Note 3 and do extraction
  3. javaScript基础之闭包
  4. linux_2.6内核内存缓冲与I/O调度机制:
  5. Effective C++ 总结(二)
  6. JavaScript 客户端JavaScript之 脚本化文档
  7. 指针和Const限定符
  8. Hive自己定义函数的使用——useragent解析
  9. [刷题]算法竞赛入门经典 3-7/UVa1368 3-8/UVa202 3-9/UVa10340
  10. vijos1760题解
  11. Scrapy架构及其组件之间的交互
  12. 转-CSS padding margin border属性详解
  13. 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
  14. BZOJ1101: [POI2007]Zap(莫比乌斯反演)
  15. 导入导出Oracle
  16. Java并发编程(十一)线程池的使用
  17. 什么是Java优先级队列(Priority Queue)?
  18. Floyd判圈算法 UVA 11549 - Calculator Conundrum
  19. POJ 3666 Making the Grade(二维DP)
  20. 【CF827F】Dirty Arkady&#39;s Kitchen DP

热门文章

  1. Python获取网页html代码
  2. 访问需要HTTP Basic Authentication认证的资源的c#的实现
  3. spring: 我是如何解决循环依赖的?
  4. &ldquo;开源、共享、创新&rdquo; 2020 中国.NET开发者大会小结
  5. 8. 格式化器大一统 -- Spring的Formatter抽象
  6. JavaScript DOM编程艺术(第2版)的简单总结
  7. 行业动态 | 利用Cassandra数据库揭开家族祖先的秘密
  8. MySQL select 语句指定字段查询
  9. 【Java】Java关键字、含义
  10. 【Linux】dlopen failed: /lib/lsiRAID.so: cannot open shared object file: No such file or directory