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.
|
|
|
|
#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时的前缀和
|
|
|
|
|
|
|
|
|
|
//一般状态压缩DP,n<=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;
|
|
|
|
|
}
|