【每日一题】codeforces 577B(1900)(抽屉原理 and DP)

每日一题,坚持使我强大


今日份快乐: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;
}

坚持的时候很狼狈,等成功以后,丑的还是丑的🤭

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读