You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

44 lines
2.2 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9 + 7; //取模
const int N = 1 << 24; //最多24个数字
int n; //实际n个数字
int k; //k个不允许出现的数字
int full; //将所有位置全部为1时的拉满状态对应的十进制数
unordered_set<int> no_permit; //有哪些数字是不允许出现在前缀和中的
int f[N]; //f[i]表示状态为i时的方案数
LL sum[N]; //sum[i]表示当状态为i时的前缀和
//一般状态压缩DPn<=12,这里的n<=24看来暴力的状态压缩要挂~
int lowbit(int x) {
return x & (-x);
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> sum[1 << i]; //按二进制位置映射的十进制数来存储(状态压缩式的存储方法)
//前缀和不允许出现的数字
cin >> k;
int x;
for (int i = 0; i < k; i++)cin >> x, no_permit.insert(x);
//状态为0时表示一个全部都不选择那么前缀和是0肯定不在给定的k个正整数之中。因为正整数都大于0这是一种唯一方案。
f[0] = 1, full = (1 << n) - 1;//满状态
//枚举现在的状态
for (int i = 1; i <= full; i++) {
//每次新的状态sum[i] = 仅有末尾1的状态 + 去掉末尾1的状态而二者都在先前求过了可以O(1)的求出sum[i] 的值
sum[i] = sum[i & ~lowbit(i)] + sum[lowbit(i)];
//sum[i] += sum[i - 1]; 这种传统方法是不可以的因为sum数组并不是按一个个数字按次序保存的而是按二进制数位方式保存的
//这所以这样保存是因为状态压缩DP是需要枚举每一个数位每个数位与传统方式的前缀和不符需要特殊记忆一下这个方法
//如果前缀和等于限定的数,那么是不合法的
if (no_permit.count(sum[i]))continue;
//快速找到所有位置为1的
for (int j = i; j; j -= lowbit(j))//枚举可能的上一次状态
f[i] = (f[i] + f[i & ~lowbit(j)]) % MOD; //去掉j位为1的 i & ~lowbit(j)
}
printf("%d\n", f[full]);
return 0;
}