【每日一题】洛谷 P1356(DP)

想她一次就背十个单词,当我英语过六级后,我就去告诉她,我很在意她
一天一道数论题,当我可以秒杀数论题的时候,就开始做 DP


今日份快乐:洛谷 P1356 传送门
明天份快乐:洛谷 P2085 传送门


题目大意

有一个序列,有 n 个数,现使序列使用 ‘+’ 和 ‘-’ 连接,使得序列可以计算结果。
问:是否可以得到被 k 整除的结果

分析

这个就是个披着数论的DP题,和数论有关的内容有两点:

  • 通过取模减小数据范围,k 的范围是 (1~100)就很方便
  • 通过取模避免负数出现的情况,储存数据的时候我们通过取模,把所有的数据都存成正数

第一点:当 k = 5 时,1 、6 和 11 三个数组对结果的贡献是一样的,1 % 5 = 6 % 5 = 11 % 5 = 1。
第二点:在取模时,我们为了避免负数一般都会这样运算 (x % k + k)% k,这样的结果就避免了负数
eg: -2 % 5 = -2,(-2 % 5 + 5)% 5 = 3
我们在统计 -2 时,可以把 -2 统计为 3。-2 减去 3 以后就是可以被 5 整除的 -5,而 3 减去 3 以后得到的同样是可以被乘除的 0。

给出状态转移方程 dp[i][j] = dp[i-1][mod(j - x)] | dp[i-1][mod(j+x)] 。位运算 |:两者有一个为 1 则结果为 1 。
dp[i][j] 表示加上或者减去第i个数后取模是否等于 j ,我们在判断 dp[i][j] 的时候就要通过看 dp[i-1][j-x] 或者 dp[i-1]j[j+x]是否存在(要考虑取模,这个是没去模的)。 最后我们只需要判断 dp[n][0] 是不是出现过就ok,详解在代码中。

给不会明白DP的小伙伴解释一下状态转移:
假设:k = 10, dp[5][4] = 1(也就是对于前 5 个数,通过‘+’,‘-‘运算可以得出结果 4)(这就是状态,在第五个数,结果为 4 的状态为真(存在))
再说说状态转移,如果第六个数 x = 6,对于可以在第六个数出现状态,我们现在只考虑 dp[5][4] 贡献。
因为 ((4+x) % 10 + 10) % 10= 0,((4 - x) % 10 + 10) % 10= 8,所以说 dp[6][0] 和 dp[6][8] 的是由 dp[5][4] 的状态转移过来的
在这里插入图片描述
这里要注意:在第五个的数的时候不一定只有dp[5][4] 为真,我们在转移时要对所有第五个数存在的状态进行转移

代码


#include <bits/stdc++.h>

int n, k;
int dp[10005][105];
int mod(int x){       // 取模,避免取模结果出现负数
	return (x % k + k) % k;
}	

int main(){
	
	ios::sync_with_stdio(false); 	//关闭输入流,减少用cin输入的时间
	
	int ncase;
	cin >> ncase;
	
	while(ncase--){     // 多测试样例
		
		cin >> n >> k;
		memset(dp, false, sizeof(dp));  // 初始化DP数组,false 代表没出现过当前状态的数
		
		int x; 
		cin >> x;
		dp[1][mod(x)] = true;      // 初始化 i = 1 时的状态
		for(int i = 2; i <= n; i++){    
			cin >> x;
			for(int j = 0; j < k; j++){     // 进行状态转移
				dp[i][j] = dp[i-1][mod(j - x)] | dp[i-1][mod(j+x)]; 
			}
		}
		
		if(dp[n][0]) cout << "Divisible" << endl;   // 如果出现就是 Divisible
		else cout << "Not divisible" << endl;	    // 没出现就是 Not divisible
		
	}
	
	return 0;
}

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值