【题目链接】
ybt 1195:判断整除
OpenJudge NOI 2.6 3531:判断整除
【题目考点】
1. 动态规划:线性动规
【解题思路】
每次添加的数字可能是正数,可能是负数,这样构成一个数字序列。
1. 状态定义:
考虑如下情况:如果最后一个输入的数字是x,前面的数字加和为s
- 如果添加x后加和能被k整除,那么有 ( s + x ) % k = 0 (s+x)\%k = 0 (s+x)%k=0。即如果 s % k = ( k − x % k ) % k s\%k = (k-x\%k)\%k s%k=(k−x%k)%k,那么添加x后加和能被k整除。
- 如果添加-x后能被k整除,那么有 ( s − x ) % k = 0 (s-x)\%k = 0 (s−x)%k=0, 即如果 s % k = x % k s\%k = x\%k s%k=x%k,那么添加-x后加和能被k整除。
如果以上两种情况中有一种成立,那么就可以得到能被k整除的加和。
而要判断上述条件是否成立,必须要可以做到判断前i个数字的加和整除k后能否得到某数字j。
因此,设计状态定义:
dp[i][j]
为是否存在数字序列方案可以使得前i个数字的加和除k余j,如果存在,值为true;不存在,值为false。
初始状态:dp[0][0]
为前0个数字的加和(加和为0)是否除k余0,是的。因此dp[0][0] = true
。
要求的结果为:前n个数字加和是否可以除k余0,即dp[n][0]
2. 状态转移方程
考虑要使前i个数字的加和除k后余j,前i-1个数字的加和必须如何?
设输入的第
i
i
i个数字为
v
v
v,前
i
−
1
i-1
i−1个数字的加和为
s
s
s:
- 如果添加数字 v v v后加和除 k k k余 j j j,那么有 ( s + v ) % k = j (s+v)\%k = j (s+v)%k=j,即 s % k = ( j + k − v % k ) % k s\%k = (j+k-v\%k)\%k s%k=(j+k−v%k)%k
推导过程:
由于 ( s + v ) % k = j (s+v)\%k = j (s+v)%k=j,那么一定有 j < k j<k j<k,所以 j = j % k = j % k % k j = j\%k=j\%k\%k j=j%k=j%k%k
( s + v ) % k = j ⇒ (s+v)\%k = j \Rightarrow (s+v)%k=j⇒
( s % k + v % k ) % k = j % k % k (s\%k+v\%k)\%k = j\%k\%k (s%k+v%k)%k=j%k%k
两边为 a % k = b % k a\%k=b\%k a%k=b%k的形式,两边被除数加上相同的正整数后,余数一定还是相等的,即 ( a + c ) % k = ( b + c ) % k (a+c)\%k=(b+c)\%k (a+c)%k=(b+c)%k
让两边被除数加上 k − v % k k-v\%k k−v%k
( s % k + v % k + k − v % k ) % k = ( j % k + k − v % k ) % k ⇒ (s\%k+v\%k+k-v\%k)\%k =( j\%k+k-v\%k)\%k \Rightarrow (s%k+v%k+k−v%k)%k=(j%k+k−v%k)%k⇒
( s % k + k ) % k = ( j % k + k − v % k ) % k ⇒ (s\%k+k)\%k =( j\%k+k-v\%k)\%k\Rightarrow (s%k+k)%k=(j%k+k−v%k)%k⇒
s % k = ( j + k − v % k ) % k s\%k =( j+k-v\%k)\%k s%k=(j+k−v%k)%k
- 如果添加数字 − v -v −v后加和除 k k k余 j j j,那么有 ( s − v ) % k = j (s-v)\%k = j (s−v)%k=j,即 s % k = ( j + v ) % k s\%k = (j+v)\%k s%k=(j+v)%k
推导过程:
由于 ( s − v ) % k = j (s-v)\%k = j (s−v)%k=j,那么一定有 j < k j<k j<k,所以 j = j % k = j % k % k j = j\%k=j\%k\%k j=j%k=j%k%k
( s − v ) % k = j ⇒ (s-v)\%k = j \Rightarrow (s−v)%k=j⇒
( s % k − v % k + k ) % k = j % k % k (s\%k-v\%k+k)\%k = j\%k\%k (s%k−v%k+k)%k=j%k%k
两边为 a % k = b % k a\%k=b\%k a%k=b%k的形式,两边被除数加上相同的正整数后,余数一定还是相等的,即 ( a + c ) % k = ( b + c ) % k (a+c)\%k=(b+c)\%k (a+c)%k=(b+c)%k
让两边被除数加上 v % k v\%k v%k
( s % k − v % k + v % k + k ) % k = ( j % k + v % k ) % k ⇒ (s\%k-v\%k+v\%k+k)\%k =( j\%k+v\%k)\%k \Rightarrow (s%k−v%k+v%k+k)%k=(j%k+v%k)%k⇒
s % k % k = ( j % k + v % k ) % k ⇒ s\%k\%k =( j\%k+v\%k)\%k\Rightarrow s%k%k=(j%k+v%k)%k⇒
s % k = ( j + v ) % k s\%k =( j+v)\%k s%k=(j+v)%k
因此,如果前
i
−
1
i-1
i−1个数字的加和
s
s
s满足
s
%
k
=
(
j
+
k
−
v
%
k
)
%
k
s\%k = (j+k-v\%k)\%k
s%k=(j+k−v%k)%k,那么接下来添加数字
v
v
v,得到的前
i
i
i个数的加和满足除k余j。
如果
i
−
1
i-1
i−1个数字的加和
s
s
s满足
s
%
k
=
(
j
+
v
)
%
k
s\%k = (j+v)\%k
s%k=(j+v)%k,那么接下来添加数字
−
v
-v
−v,得到的前
i
i
i个数的加和满足除k余j。
也可以说,只要前
i
−
1
i-1
i−1个数字的加和
s
s
s满足
s
%
k
=
(
j
+
k
−
v
%
k
)
%
k
s\%k = (j+k-v\%k)\%k
s%k=(j+k−v%k)%k或
s
%
k
=
(
j
+
v
)
%
k
s\%k = (j+v)\%k
s%k=(j+v)%k,都可以通过添加v或v的相反数,来让前
i
i
i个数的加和满足除k余j。
因此可以得出状态转移方程:
dp[i][j] = dp[i-1][j+k-v%k] || dp[i-1][(j+v)%k]
其中v为第i个数字。
或者先将各个数字输入到数组a中,a[i]
为第i个数字,状态转移方程为:
dp[i][j] = dp[i-1][j+k-a[i]%k] || dp[i-1][(j+a[i])%k]
【题解代码】
解法1:线性动规
- 写法1:先输入数据到数组,再求状态
#include <bits/stdc++.h>
using namespace std;
bool dp[10005][105];//dp[i][j]表示前i个数字(无论正负)的和结果模k能不能得到j
int a[10005];
int main()
{
int n, k, v;
cin >> n >> k;
dp[0][0] = true;
for(int i = 1; i <= n; ++i)
cin >> a[i];
for(int i = 1; i <= n; ++i)
for(int j = 0; j < k; ++j)//j是除k得到的余数,范围为0~k-1
dp[i][j] = dp[i-1][(k+j-a[i]%k)%k] || dp[i-1][(j+a[i])%k];
cout << (dp[n][0] ? "YES" : "NO");
return 0;
}
- 写法2:一边输入一边求状态
#include <bits/stdc++.h>
using namespace std;
bool dp[10005][105];//dp[i][j]表示前i个数字(无论正负)的和结果模k能不能得到j
int main()
{
int n, k, v;
cin >> n >> k;
dp[0][0] = true;
for(int i = 1; i <= n; ++i)
{
cin >> v;
for(int j = 0; j < k; ++j)//j是除k得到的余数,范围为0~k-1
dp[i][j] = dp[i-1][(k+j-v%k)%k] || dp[i-1][(j+v)%k];
}
cout << (dp[n][0] ? "YES" : "NO");
return 0;
}