HackerRank# Wet Shark and Two Subsequences

原题地址

 

对于给定的两个约束条件,可以通过联立方程组直接解出子序列A的和和子序列B的和,即sum(A) = (r + s) / 2,sum(B) = (r - s) / 2,假设|A|=|B|=n

所以问题变成了,在一个数组中求长度为n且子序列和为sum(A)或sum(B)有多少个。

假设count(n, s)表示长度为n且子序列和为s有多少个,则要求的是count(n, sum(A)) * count(n, sum(B)),其中1<=n<=m

或者通俗来说就是m个k-sum问题

 

求k-sum,如果用搜索的方法,时间复杂度大概是n^k量级(可以优化到n^(k - 1) + nlogn),何况现在要求的是m个k-sum问题,即使排序+剪枝优化肯定也会超时(别问我是怎么知道的)

所以只能选择动归,因为每次求k-sum的过程存在大量重复计算。

令f[i][j][k]表示从第i个元素开始,子序列长度为j,子序列和为k,这样的子序列有多少个

那么有地推公式:f[i][j][k] = f[i+1][j - 1][k - a[i]] + f[i + 1][j][k]

这个公式其实挺straightforward,跟背包问题是一样的。

 

由于sum(A)和sum(B)最大不过2000,所以k的范围是2000,另外i和j的范围分别是m的范围100,所以如果不做任何状态压缩,占用空间大概是2000*100*100*4*8约为640MB(用int存储),显然要爆。所以还必须状态压缩。

分析地推公式,明显压缩i那维,而且不难看出j和k必须从后向前推。还是跟背包问题一样。

 

代码:

 1 #include <cmath>

 2 #include <cstdio>

 3 #include <vector>

 4 #include <iostream>

 5 #include <algorithm>

 6 #include <cstring>

 7 using namespace std;

 8 

 9 #define MAX_SIZE 128

10 #define MAX_SUM 2048

11 #define MOD 1000000007

12 

13 long long a[MAX_SIZE];

14 int m, r, s;

15 long long f[MAX_SIZE][MAX_SUM];

16 

17 int main() {

18     /* Enter your code here. Read input from STDIN. Print output to STDOUT */

19     int sa, sb;

20     long long res = 0;

21 

22     cin >> m >> r >> s;

23     sa = (r + s) / 2;

24     sb = (r - s) / 2;

25     for (int i = 0; i < m; i++)

26         cin >> a[i];

27 

28     memset(f, 0, sizeof(f));

29     f[0][0] = 1;

30     for (int i = m - 1; i >= 0; i--) {

31         for (int j = m; j >= 1; j--) {

32             for (int k = 2000; k >= 0; k--) {

33                 if (k >= a[i])

34                     f[j][k] = (f[j][k] + f[j - 1][k - a[i]]) % MOD;

35             }

36         }

37     }

38 

39     for (int i = 1; i <= m; i++)

40         res = (res + (f[i][sa] * f[i][sb]) % MOD) % MOD;

41     

42     cout << res << endl;

43     return 0;

44 }

 

第一次用的int,结果提交以后有些case是WA,考虑到DP问题基本上不会出现WA的情况,所以断定应该是数据溢出了,换成long long果然就没问题了。

你可能感兴趣的:(sequence)