匹配价值
匹配价值
给定一个字符串集合 $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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16691801.html