匹配价值

匹配价值

给定一个字符串集合 $S$,$S$ 中包含 $m$ 个长度为 $n$ 的 $01$ 字符串,集合中可能包含重复元素。

给定一个长度为 $n$ 的整数序列 $w_1,w_2, \dots ,w_n$。

关于两个长度为 $n$ 的 $01$ 字符串 $s,t$ 的匹配价值 $V$,其具体计算方法如下:

  • 设字符串 $s$ 的各位字符从左到右依次为 $s_1,s_2, \dots ,s_n$。
  • 设字符串 $t$ 的各位字符从左到右依次为 $t_1,t_2, \dots ,t_n$。
  • 初始时,$V=0$。
  • 对于所有 $i(1 \leq i \leq n)$,如果 $s_i=t_i$,则 $V$ 加上 $w_i$。
  • 最终得到的 $V$ 即为两字符串的匹配价值。

现在,给定 $q$ 个询问,每个询问包含一个长度为 $n$ 的 $01$ 字符串 $t$ 以及一个整数 $k$,具体询问内容为:请你计算并输出集合 $S$ 中有多少个元素满足,与给定字符串 $t$ 的匹配价值不大于 $k$。

注意,如果集合中多个相同的元素均满足询问条件,则每个元素均应被计数。

输入格式

第一行包含三个整数 $n,m,q$。

第二行包含 $n$ 个整数 $w_1,w_2, \dots ,w_n$。

接下来 $m$ 行,每行包含一个长度为 $n$ 的 $01$ 字符串,表示集合 $S$ 中的一个元素。

最后 $q$ 行,每行包含一个长度为 $n$ 的 $01$ 字符串 $t$ 和一个整数 $k$,表示一个询问。

输出格式

每个询问输出一行答案,一个整数,表示满足询问条件的元素个数。

数据范围

前 $3$ 个测试点满足 $1 \leq m,q \leq 5$。
所有测试点满足 $1 \leq n \leq 12$,$1 \leq m,q \leq 5 \times {10}^{5}$,$0 \leq w_i \leq 100$,$0 \leq k \leq 100$。

输入样例1:

2 4 5
40 20
01
01
10
11
00 20
00 40
11 20
11 40
11 60

输出样例1:

2
4
2
3
4

输入样例2:

1 2 4
100
0
1
0 0
0 100
1 0
1 100

输出样例2:

1 1
2 2
3 1
4 2

 

解题思路

  这题思路是预处理。可以发现直接暴力的话肯定会超时,暴力枚举是为了在集合中统计与字符串匹配权值不超过$k$的个数,因此可以想到把枚举的过程进行预处理,查询的时候就可以控制到$O(1)$。

  $01$字符串的最大长度为$12$,因此最多有$2^{12}$种不同的字符串,可以预处理两两配对的情况,一共有$2^{24}$种情况,这个计算量是可以过的,因此可以先预处理出这$2^{24}$种情况,然后算一下每种情况对应的权值是多少。同时在询问的时候权值是不超过$100$的,只有当计算的权值不超过$100$时才记录下来,因此开一个数组$s[i,j]$表示输入的字符串是$i$,权值为$j$时,集合中与$i$匹配的字符串个数。因为询问求的是权值不超过$k$的情况,因此预处理完后还需要对$s$数组求前缀和。

  在字符串进行匹配求权值时用位运算,就可以把这部分的计算量优化到$O(1)$。如果有$01$串$a$和$b$,为了求$a$和$b$中有哪些位是相同的,可以先异或再对结果按位取反,即$\sim (a \wedge b)$,因为$01$串的位数不是固定$32$位,这个结果会使得高位全是$1$,因此事实上应该是$a \wedge b \wedge ((1 << n)-1)$,其中$n$是$01$串的位数。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1 << 12, M = 110;
 5 
 6 int n, m, k;
 7 int w[12], mp[N];
 8 int cnt[N];
 9 int s[N][M];
10 
11 int get(char *str) {
12     int st = 0;
13     // 原字符串低位在前高位在后,转换后01串后低位在后高位在前,因此需要反过来处理
14     for (int i = n - 1; i >= 0; i--) {
15         st = st << 1 | str[i] - '0';
16     }
17     return st;
18 }
19 
20 int main() {
21     scanf("%d %d %d", &n, &m, &k);
22     
23     for (int i = 0; i < n; i++) {
24         scanf("%d", w + i);
25     }
26     for (int i = 0; i < 1 << n; i++) {  // 预处理2^n种情况对应的权值
27         for (int j = 0; j < n; j++) {
28             if (i >> j & 1) mp[i] += w[j];
29         }
30     }
31     
32     while (m--) {
33         char str[15];
34         scanf("%s", str);
35         cnt[get(str)]++;    // 统计集合中该字符串出现的次数
36     }
37     
38     for (int i = 0; i < 1 << n; i++) {      // i是询问的字符串
39         for (int j = 0; j < 1 << n; j++) {  // j是集合中的字符串
40             int t = i ^ j ^ (1 << n) - 1;   // 求i和j相同的位
41             if (mp[t] <= 100) s[i][mp[t]] += cnt[j];    // 如果权值不超过100就累加集合中j的次数(如果不存在j则为0)
42         }
43     }
44     
45     for (int i = 0; i < 1 << n; i++) {  // 求前缀和
46         for (int j = 1; j <= 100; j++) {
47             s[i][j] += s[i][j - 1];
48         }
49     }
50     
51     while (k--) {
52         char str[15];
53         int t;
54         scanf("%s %d", str, &t);
55         printf("%d\n", s[get(str)][t]);
56     }
57     
58     return 0;
59 }

 

参考资料

  AcWing 4614. 匹配价值(AcWing杯 - 周赛):https://www.acwing.com/video/4317/  

posted @ 2022-09-14 09:02  onlyblues  阅读(35)  评论(0编辑  收藏  举报
Web Analytics