1. 程式人生 > >Codeforces #319(Div.2) B. Modulo Sum (動態規劃)

Codeforces #319(Div.2) B. Modulo Sum (動態規劃)

Note

In the first sample test you can choose numbers 2 and 3, the sum of which is divisible by 5.

In the second sample test the single non-empty subsequence of numbers is a single number 5. Number 5 is not divisible by 6, that is, the sought subsequence doesn't exist.

In the third sample test you need to choose two numbers 3

 on the ends.

In the fourth sample test you can take the whole subsequence.

題意:兩個數n和m,還有a1-an的n個數,判斷是否存在該數列的一個子序列,使得子序列元素的和可以被m整除。

看了下官方題解:

分兩種情況:n>m 和 n<=m。

如果n>m, 可以判斷輸出一定為“Yes”。求出前 i 個數的和 S1-Sn,有鴿巢原理,可以知道至少有兩個數列和對m取模的結果相等,假設為Sl%m=Sr%m,則可以知道(Sl-Sr)%m==0,於是 [ l+1,r ]就是所求的子序列。

如果n<=m, 用動態規劃解決,O(m^2)。dp[i][r]表示到了第i個數,前面子序列的和對m取模是否能夠等於r,於是狀態轉移方程就是,如果前面一個數,有dp[i-1][r], 則後面一個數可以選擇ai使得dp[i][(r+ai)%m]為1,或者不選ai,使dp[i][r]=1。這樣做的目的是為了盡最大努力使得後面的數能夠得到對m取模為某個數的子序列和。遍歷一遍dp[i][0]就得到本題的答案。

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int maxn = 1010;

int dp[maxn][maxn], a[maxn];

int main()
{
	int n, m;
	while (scanf("%d%d", &n, &m) != EOF)
	{
		if (n >= m)
		{
			for (int i = 0; i < n; i++)
				scanf("%d", &m);
			printf("YES\n");
			continue;
		}

		for (int i = 0; i < n; i++)
			scanf("%d", &a[i]);
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; i++)
		{
			if (!i)
				dp[0][(a[i] % m)] = 1;
			else
			{
				dp[i][(a[i] % m)] = 1;
				for (int j = 0; j < m; j++)
				{
					if (dp[i - 1][j])
					{
						dp[i][(j + a[i]) % m] = 1;
						dp[i][j] = dp[i - 1][j];
					}
				}
			}
		}
		int flag = 0;
		for (int i = 0; i < n; i++)
		{
			if (dp[i][0])
				flag = 1;
		}
		if (flag)
			printf("YES\n");
		else
			printf("NO\n");
	}
}