信息学奥赛一本通 1195:判断整除 | OpenJudge NOI 2.6 3531:判断整除

【题目链接】

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=(kx%k)%k,那么添加x后加和能被k整除。
  • 如果添加-x后能被k整除,那么有 ( s − x ) % k = 0 (s-x)\%k = 0 (sx)%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 i1个数字的加和为 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+kv%k)%k

推导过程:
由于 ( s + v ) % k = j (s+v)\%k = j (s+v)%k=j,那么一定有 j < k jj<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 kv%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+kv%k)%k=(j%k+kv%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+kv%k)%k
s % k = ( j + k − v % k ) % k s\%k =( j+k-v\%k)\%k s%k=(j+kv%k)%k

  • 如果添加数字 − v -v v后加和除 k k k j j j,那么有 ( s − v ) % k = j (s-v)\%k = j (sv)%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 (sv)%k=j,那么一定有 j < k jj<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 (sv)%k=j
( s % k − v % k + k ) % k = j % k % k (s\%k-v\%k+k)\%k = j\%k\%k (s%kv%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%kv%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 i1个数字的加和 s s s满足 s % k = ( j + k − v % k ) % k s\%k = (j+k-v\%k)\%k s%k=(j+kv%k)%k,那么接下来添加数字 v v v,得到的前 i i i个数的加和满足除k余j。
如果 i − 1 i-1 i1个数字的加和 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 i1个数字的加和 s s s满足 s % k = ( j + k − v % k ) % k s\%k = (j+k-v\%k)\%k s%k=(j+kv%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 
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 
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;
}

你可能感兴趣的:(信息学奥赛一本通题解,OpenJudge,NOI题解,动态规划)