每日一题,坚持使我强大
今日份快乐:codeforces 577B 传送门
明天份快乐:codeforces 1348D 传送门 (感觉给自己挖了个坑)
题目大意:
给 n 个数,问是否能找到一个子序列使得他们的和可以被 m 整除
分析
如果这是一个数据规模比较小的题,直接 DP 就可以过。我们可以利用抽屉原理来消减数据范围。
抽屉原理:如果 5 个抽屉放 6 个东西,那么肯定最少要有一个抽屉放两个或更多的东西
抽屉原理在取模计算的时候有很大的意义,给个实例来理解一下
eg: n = 7, m = 7
i 1 2 3 4 5 6 7 ai 12 8 6 17 20 34 36 sumi 12 20 36 53 73 107 143 sumi % 5 5 6 1 4 3 2 3 很明显 ( sum7 - sum 5 ) % 7 = 0,也就是说 (a6 + a7) % 7 = 0
这里的数据没有被 7 整除的数据,也就是说是,取余过后是七个数放在六个抽屉,最少要有两个前缀和的余数相同。
当然如果上例中出现可以被整除时,就是七个数放在七个抽屉里,虽然没余数相同的数,但是有直接满足要求的数
由上,我们就可以得出结论,当 n
≥
\geq
≥ m 时,一定存在一个序列使得他们的和被 m 整除
我们再来看当 n < m 时,因为 m
≤
\leq
≤ 1e3,,我们可以直接DP来求解
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int a[maxn] = {0};
bool dp[1005][1005] = {false};
int main() {
ios::sync_with_stdio(false);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i] %= m;
}
bool ok = false;
if(n >= m) ok = true, n = 0; // 这里让 n = 0,下边的循环就不会执行
for(int i = 1; i <= n; i++){
dp[i][a[i]] = true; // 初始化
for(int j = 0; j < m; j++){
if(dp[i-1][j]) dp[i][j] = true; // 传递上一位的状态
if(dp[i][j]) dp[i+1][(j + a[i+1]) % m] = true; // 状态转移
}
}
if(dp[n][0]) ok = true; // 判断答案
if(ok) cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
坚持的时候很狼狈,等成功以后,丑的还是丑的🤭