总时间限制:
1000ms
内存限制:
65536kB
描述
一个给定的正整数序列,在每个数之前都插入+号或-号后计算它们的和。比如序列:1、2、4共有8种可能的序列:
(+1) + (+2) + (+4) = 7
(+1) + (+2) + (-4) = -1
(+1) + (-2) + (+4) = 3
(+1) + (-2) + (-4) = -5
(-1) + (+2) + (+4) = 5
(-1) + (+2) + (-4) = -3
(-1) + (-2) + (+4) = 1
(-1) + (-2) + (-4) = -7
所有结果中至少有一个可被整数k整除,我们则称此正整数序列可被k整除。例如上述序列可以被3、5、7整除,而不能被2、4、6、8……整除。注意:0、-3、-6、-9……都可以认为是3的倍数。
输入
输入的第一行包含两个数:N(2 < N < 10000)和k(2 < k< 100),其中N代表一共有N个数,k代表被除数。第二行给出序列中的N个整数,这些整数的取值范围都0到10000之间(可能重复)。
输出
如果此正整数序列可被k整除,则输出YES,否则输出NO。(注意:都是大写字母)
样例输入
3 2 1 2 4
样例输出
NO
分析:
与上一题(糖果)相似,这又是一道与余数有关的动态规划。
每个数的加或减状态决定最终的和,题目说明0,-3,-6都是三的倍数,那么结果得到的-x 与 x 就是等价的。分析本题的描述里的几个等式,发现某个数只取正或负的结果 与 该数正负都取得到的结果是一样的(就是说:你看前四个等式的结果与后面四个等式的结果是不是一样的(忽略正负),而这个两对等式的“1”就是只取正或负。)
利用上述说明,我们可以将第一个数固定为正。
说说动规思想:
定义一个布尔型二维数组dp[i][j] (i代表对前i个数进行计算,j代表计算结果的余数)
首先初始化第一个值dp[1][firstnum%k] (k 就是题目上的k)
基于此创建状态方程:
if(dp[i-1][j]) {
dp[i][(j+nextnum)%k]=true;
dp[i][abs((j-nextnum)%k)]=true;
}
就是说:如果从第一个数算到第i-1数能算出余数为j的结果话 那么就拿这个结果进一步与第i个数进行加或减,令它为true就表示这种结果存在。
如果最后dp[n][0]这个结果存在,不就说明存在某种方式使得最后余数为零呗,然后输出YES。
词穷了,上代码:
#include<iostream>
#include<math.h>
using namespace std;
bool dp[10005][105];
int num[10005];
int main() {
int n, k; cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> num[i];
num[i] %= k;//直接取该数的余数进行操作
}
dp[1][num[1]] == true;//初始化
for(int i=2;i<=n;i++)//从第二个数开始
for(int j=0;j<k;j++)
if (dp[i - 1][j]) {//传递性:上一个数及之前的数和的余数为j,就继续与下一个数相加或相减得到新余数
dp[i][(j + num[i]) % k] = true;
dp[i][abs((j - num[i]) % k)] = true;
}
if (dp[n][0]) cout << "YES";//如果传到最后能组合成余数为零的情况,即成功!
else cout << "NO";
return 0;
}
不懂评论区问问呗。
感谢阅读!!!❤