Description
很多时候,数据范围是一道题目的突破口,但有时,也会是出题人迷惑你的陷阱。 现在,cgg给你01串集合S,S中包含m个长度为n的01串。(01串就是只包含’0’,'1’两种字符的字符串)
给你一个长度为n的数组w,两个01串之间存在一种W值,两个01串的第i位相同的话,W值就会加上wi,否则W值加0。例如:w = [4,5,3,6],“1001”,"1100"的W值就等于w1+w3=4+3=7,因为两串的第一位和第三位相同。
现在要进行q次询问,每次给你一个长度为n的01串t和整数k,询问你集合S中会有多少个01串与t的W值小于等于k。
Input
第一行给你三个整数n,m,q,分别表示所有01串的长度,集合S中的01串个数,查询次数。(1 <= n <= 12 , 1 <= q,m <= 500,000)
第二行给你n个整数,表示数组w。(0 <= wi <= 100)
接下来m行,每行一个长度为n的01串,表示集合S。
接下来q行,每次给你一个长度为n的01串t和整数k,询问你集合S中会有多少个01串与t的W值小于等于k。(0 <= k <= 100)
Output
输出每次询问,输出所求答案。
Examples
Input1
2 4 5
40 20
01
01
10
11
00 20
00 40
11 20
11 40
11 60
Output1
2
4
2
3
4
Input2
1 2 4
100
0
1
0 0
0 100
1 0
1 100
Output2
1
2
1
2
Solution
正如题目所说,有时候题目数据只是陷阱
虽然q和m都是5e5,但长度最大为12的01串最多只有(1<<12) 种
所以我们可以
O
(
2
n
∗
2
n
∗
n
)
O(2^n * 2^n * n)
O(2n∗2n∗n)处理答案,O(1)解决每次询问
维护前缀和可以套树状数组,就是有亿点点卡常
Code
int n,m,q;
int lim = 103, tot = 0;
int w[maxn];
int bit[(1<<12)+3][110];
int cnt[(1<<12)+3];
int ans[(1<<12)+3][110];
int s[maxn];
void init() {
for(int i = 0;i < (1 << n);++i) {
for(int j = 1;j <= tot;++j) {
int tmp = 0;
for(int k = n-1;k >= 0;--k) {
if((bool)(i & (1<<k)) == (bool)(s[j] & (1<<k))) {
tmp += w[n-k];
if(tmp > 100) break;
}
}
if(tmp > 100) continue;
ans[i][tmp]+=cnt[s[j]];
}
for(int k = 1;k <= 100;++k) {
ans[i][k] += ans[i][k-1];
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&q);
for(int i = 1;i <= n;++i) scanf("%d",&w[i]);
for(int i = 1;i <= m;++i) {
int tmp = 0;
for(int j = 1, x;j <= n;++j) {
scanf("%1d",&x);
tmp = (tmp * 2 + x);
}
s[i] = tmp;
cnt[tmp]++;
}
sort(s + 1,s + 1 + m);
tot = unique(s + 1,s + 1 + m) - s - 1;
init();
for(int i = 1;i <= q;++i) {
int tmp = 0,k;
for(int j = 1,x;j <= n;++j) {
scanf("%1d",&x);
tmp = (tmp * 2 + x);
}
scanf("%d",&k);
printf("%d\n", ans[tmp][k]);
}
return 0;
}