【HNOI 2018】寻宝游戏

Problem

Description

某大学每年都会有一次 \(Mystery\ Hunt\) 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会。

作为新生的你,对这个活动非常感兴趣。你每天都要从西向东经过教学楼一条很长的走廊,这条走廊是如此的长,以至于它被人戏称为 \(Infinite\ Corridor\) 。一次,你经过这条走廊的时候,注意到在走廊的墙壁上隐藏着 \(n\) 个等长的二进制的数字,长度均为 \(m\) 。你从西向东将这些数字记录了下来,形成一个含有 \(n\) 个数的二进制数组 \(a_1, a_​2 \cdots, a_​n\)

很快,在最新的一期 \(Voo\ Doo\) 杂志上,你发现了 \(q\) 个长度也为 \(m\) 的二进制串 \(r_1, r_​2, \cdots, r_​q\)

聪明的你很快发现了这些数字的含义。

保持数组 \(a_1, a_​2 \cdots, a_n\) 的元素顺序不变,你可以在它们之间插入 \(\land\)(按位与运算)或者 \(\vee\)(按位或运算)两种二进制运算符。例如: \(11011 \land 00111 = 00011,11011 \vee 00111 = 11111\)

你需要插入恰好 \(n\) 个运算符,相邻两个数之间恰好一个,在第一个数的左边还有一个。如果我们在第一个运算符的左边补入一个 \(0\) ,这就形成了一个运算式,我们可以计算它的值。与往常一样,运算顺序是从左往右。有趣的是,出题人已经告诉你这个值的可能的集合—— \(Voo Doo\) 杂志里的那一些二进制数 \(r_1, r_​2, \cdots, r_q\) ,而解谜的方法,就是对 \(r_1, r_​2, \cdots, r_q\) 中的每一个值 \(r_i\) ,分别计算出有多少种方法填入这 \(\mathbf{n}\) 个运算符,使得这个运算式的值是 \(r_i\)

然而,\(Infinite\ Corridor\) 真的很长,这意味着数据范围可能非常大。因此,答案也可能非常大,但是你发现由于谜题的特殊性,你只需要求答案模\(1000000007(10 ^ 9 + 7\) , 一个质数)的值。

Input Format

第一行三个数\(n,m,q\),含义如题所述。

接下来\(n\)行,其中第\(i\)行有一个长度为\(m\)的二进制串,左边是最高位,表示\(a_i\)

接下来\(q\)行,其中第\(i\)行有一个长度为\(m\)的二进制串,左边是最高位,表示\(r_i\)

Output Format

输出\(q\)行,每行一个数,其中第\(i\)行表示对应于\(r_i\)的答案。

Sample

Input 1

5 5 1
01110
11011
10000
01010
00100
00100

Output 1

6

Input 2

10 10 3
0100011011
0110100101
1100010100
0111000110
1100011110
0001110100
0001101110
0110100001
1110001010
0010011101
0110011111
1101001010
0010001001

Output2

69
0
5

Explanation

Explanation for Input 1

有以下且仅有以下六个运算式的值是\(00100_{2}\):(下标2表示被标识的数是二进制数)

\(0 \land \ 01110_{2} \land \ 11011_{2} \vee {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)

\(0 \vee \ 01110_{2}\ {\vee 11011}_{2}\ \land {\ 10000}_{2}{\ \ \land 01010}_{2}{\ \vee 00100}_{2}\)

\(0 \land \ 01110_{2}\ {\vee 11011}_{2} \land {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)

\(0 \vee \ 01110_{2} \land \ 11011_{2} \land {\ 10000}_{2}{\land \ 01010}_{2}{\ \vee 00100}_{2}\)

\(0 \land \ 01110_{2}\ {\land 11011}_{2} \land {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)

\(0 \vee \ 01110_{2}\ \vee 11011_{2} \vee {\ 10000}_{2}{\ \ \vee 01010}_{2}{\ \land 00100}_{2}\)

Range

对于 \(10\%\) 的数据,\(n \leq 20, m \leq 30, \ q = 1\)

对于另外 \(20\%\) 的数据,\(n \leq 1000, m \leq 16\)

对于另外 \(40\%\) 的数据,\(n \leq 500, m \leq 1000\)

对于 \(100\%\) 的数据,\(1 \leq n \leq 1000, \ 1 \leq m \leq 5000, \ 1 \leq q \leq 1000\)

Algorithm

基数排序可能算一个???

Mentality

这个题看起来超级吓人 \(......\) 难度看起来确实挺大的样子。

但是我们应该冷静思考,先看看数据范围,\(10^3\) 左右,这大概就只有 \(O(nm)\) 的复杂度才能过了,最多带点小常数,连 \(log\) 都带不起。

辣么怎么办呢?冷静分析的话,我们决定单独来按位思考,想想每个二进制数的某一位经过运算的结果。

首先,我们发现每一位的运算符只有四种情况:

\(\&0,\&1,|0,|1\)

而我们需要学会发现这四种情况里没啥用的情况,那就是 \(\&1\)\(|0\) 这两个运算,它们对于运算结果没有任何影响。那么有影响的就只剩下了两种情况:当前位为 \(1\) 的时候,我们插入 \(|\) 符号将会使得结果必定为 \(1\) ,否则结果不变;当前位为 \(0\) 的时候,我们插入 \(\&\) 符号会使得结果必定为 \(0\) 。否则结果不变。

那么我们需要做的就变得很简单了:如果询问中这一位为 \(1\) ,那么我们必需确保最后一个有效的操作 (\(\&0\)\(|1\)) 为 \(|1\) ,如果这一位为 \(0\) ,则必须确保最后一个有效操作为 \(\&0\) 或者没有有效操作 (初始值为 \(0\)) 。

接着就是一个异常巧妙的转化了:

由于我们发现 \(\&1\)\(|0\) 这两个东西对结果没有任何影响,那么我们开开脑洞:我们认为 \(\&\)\(1\) 是等价的,\(|\)\(0\) 是等价的!

然后脑洞不要停,我们将当前位每个数前插入运算符构成一个 \(01\) 串来看,设这个串为 \(opt\) ,其中的 \(\&\) 运算代表的值就直接设为 \(1\)\(|\) 运算代表的值直接设为 \(0\) 。那么我们发现,将 \(opt\) 与当前位的数构成的 \(01\) 串对其后,如果数为 \(1\) ,而 \(opt\) 的相同位置为 \(1\) 的话,这一位上的二进制值就是相等的,这符合我们等价的脑洞。

接着,我们会异常惊喜地发现一件事情,根据之前的说法,如果我们要使运算的结果为 \(1\) ,那么最后一个有效的操作必须为 \(|1\) ,则对于最后一个有效操作的位置往后,都必须是等价操作 (也即无效操作) 。

而最后一个要求结果为 \(1\) 的有效操作位置,我们必须填入 \(|\) ,也就是在 \(opt\) 串的相同位置,我们的值为 \(0\) ,而我们此位置往后的位置又全部相等!

想到了什么吗?对!如果我们将 \(n\) 个数的当前位提出来,从后往前构成一个二进制数的话,我们必须大于当前 \(opt\) 串代表的二进制数!

举个例子:

这是 \(n\) 个数的当前位:\(1010111\)

若要使结果为 \(0\) ,假设我们最后一个有效操作在第 \(5\) 位,那么最后两位的运算操作都必须为 \(\&\) 运算,也就是 \(1\) ,那么我们的操作串如下,\(.\) 代表既可以填 \(1\) 有可以填 \(0\)
\(opt:\ ....011\)

当前位倒过来之后为:\(1110101\)
操作串倒过来之后为:\(opt:\ 110....\)
\(opt<\) \(n\) 个数当前位构成的串

总结陈词,我们设 \(b_i\)\(n\) 个数的第 \(i\) 位提出来再倒过来组成的二进制数,那么若 \(r_i\) 的第 \(j\) 位为 \(1\) ,我们必须保证我们的操作串 \(opt< b_j\) ,而我们已经证明过了,这是必然要求,那么换而言之,如果 \(opt\ge b_j\) ,这一位的结果就必定为 \(0\)

于是我们的题目变成了好玩的比大小游戏,我们只需要根据 \(m\) 个和 \(opt\) 有关的不等式得出结果就好了 \(hhhg\)

那这题的做法出来了!

先预处理出 \(b\) 数组并排序。
对于当前询问,我们找到满足 \(r_i=0\) 的最大 \(b_i\) ,设当前 \(i\)\(L\) ,满足 \(r_i=1\) 的最小 \(b_i\) ,设当前 \(i\)\(R\) ,那么最后的答案肯定就是 \(b_R-b_L\)\(hhhhh\)
当然注意当 \(L>R\) 输出 \(0\)

您问我怎么排序?当然是基数排序啊!常数只有 \(2\)\(O(nm)\) 排序算法。

但是如果您不知道基数排序我就没办法了 \(QwQ\) ,学一下吧,挺简单的。

Code

#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
int n, m, Q, tag[2], ra[5002], rb[5002], num[5001], mi[1002], t[5002];
char s[5002], q[5002];
void Mod(int &x) { x = (x % mod + mod) % mod; }
int main() {
  cin >> n >> m >> Q;
  mi[1] = 1;
  for (int i = 2; i <= n + 1; i++)
    mi[i] = (mi[i - 1] << 1) % mod;        //预处理一下幂
  for (int i = 1; i <= m; i++) ra[i] = i;  //排名
  for (int i = 1; i <= n; i++) {
    scanf("%s", s + 1);
    tag[0] = 0, tag[1] = m;  //基排的桶
    for (int j = 1; j <= m; j++) {
      Mod(num[j] += s[j] == '1' ? mi[i] : 0);  //处理 b 数组的值
      if (s[j] == '0') tag[0]++;
    }
    for (int j = m; j >= 1; j--)
      rb[tag[s[ra[j]] - '0']--] = ra[j];  //获取下一轮排名
    swap(ra, rb);                         //更新排名
  }
  ra[m + 1] = m + 1;
  num[m + 1] =
      mi[n +
         1];  //因为如果要求的结果中没有 1 ,那么操作串大小上限就是 2^n-1 了。
  while (Q--) {
    scanf("%s", q + 1);
    int l = 0, r = m + 1;
    for (int i = 1; i <= m; i++)
      if (q[ra[i]] == '1') {
        r = i;
        break;
      }  //找到最小的
    for (int i = m; i >= 1; i--)
      if (q[ra[i]] == '0') {
        l = i;
        break;
      }  //找到最大的
    if (l > r)
      cout << "0\n";
    else
      printf("%d\n", ((num[ra[r]] - num[ra[l]]) % mod + mod) % mod);
  }
}

posted @ 2019-03-09 12:07  洛水·锦依卫  阅读(289)  评论(0编辑  收藏  举报