hdoj4906 Our happy ending(2014 Multi-University Training Contest 4)

对于一个定长(size = n)的数列a, 若其存在“位置相关”的子集(含空集)使得该子集所有元素之和为k,那么将数列a计数。

其中数列a中任一元素a[i]在[0, l]内自由取值。

数据条件0≤n, k ≤ 20, 0≤ l ≤ 1e9,计数结果对mod = 1e9 + 7取模。

 

无论直接计数还是考虑从反面计数都解决不了去重的问题,只能考虑dp。

枚举数列的长度i和压缩后的状态j,并且记录在该条件下的数列选取方案数dp[i][j]。

压缩后的状态j表示对于集合{1, 2, ..., min(l, k)}的选取情况。

其中集合中第i个元素在状态j中当且仅当j的二进制串的第i位为1。

显然我们有dp[0][0] = 1。

对于长度为p的数组,第p位可以选取的元素是0,1,2,...,l

考虑p位选取1,2,...,min(l, k)

那么对于数组长度为p-1时的任一状态j,在数组后追加元素i后的状态为:

j1 = j | ((j << i) & ((1 << k) - 1)) | (1 << (i - 1))

三部分分别表示原状态,原状态每个元素与元素i求和后增加的状态,i本身。 

于是dp[p][j1] += dp[p - 1][j]。

而对于p位选取0或者大于min(l, k)的情形,j1 = j。

因此有dp[p][j] = (l - min(l, k)) * dp[p - 1][j]。

 

我们最后只需对dp[n][j],其中j第k位为1的累加即可。

由于数列每个元素非负,我们最后只关心那些集合存在和为k的子集,

而那些大于k的元素必然不是构成子集的元素,只有那些小于k的元素才对状态转移有用。

因此我们可以这样表示状态。

 

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <map>
 5 #include <string>
 6 #include <vector>
 7 #include <set>
 8 #include <cmath>
 9 #include <ctime>
10 using namespace std;
11 #define lson (u << 1)
12 #define rson (u << 1 | 1)
13 typedef __int64 ll;
14 const int maxn = 1e6 + 10;
15 const int maxm = 1050;
16 const ll mod = 1e9 + 7;
17 
18 int n, l, k;
19 ll dp[(1 << 21) + 10];
20 int main(){
21     int T;
22     scanf("%d", &T);
23     while(T--){
24         scanf("%d%d%d", &n, &k, &l);
25         ll d = abs(l - k);
26         l = min(l, k);
27         int s = (1 << k) - 1;
28         memset(dp, 0, sizeof dp);
29         dp[0] = 1;
30         while(n--){
31             for(int j = s; j >= 0; j--){
32                 ll tem = dp[j];
33                 if(!tem) continue;
34                 for(int p = 1; p <= l; p++){
35                     int nex = (1 << (p - 1)) | j | ((j << p) & s);
36                     dp[nex] = (dp[nex] + tem) % mod;
37                 }
38                 dp[j] = (tem * (1 + d)) % mod;
39             }
40         }
41         ll ans = 0;
42         for(int i = s; i >= 0; i--)
43             if(i & (1 << (k - 1)))
44                 ans = (ans + dp[i]) % mod;
45         printf("%I64d\n",ans);
46     }
47     return 0;
48 }
View Code

 

posted @ 2015-10-18 18:24  astoninfer  阅读(179)  评论(0编辑  收藏  举报