想她一次就背十个单词,当我英语过六级后,我就去告诉她,我很在意她
一天一道数论题,当我可以秒杀数论题的时候,就开始做 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;
}
坚持的时候很狼狈,等成功以后,丑的还是丑的🤭