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果然就没问题了。

posted @ 2015-04-29 00:03  李舜阳  阅读(720)  评论(0编辑  收藏  举报