zoukankan      html  css  js  c++  java
  • 【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) ,而我们已经证明过了,这是必然要求,那么换而言之,如果 (optge 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
    ";
        else
          printf("%d
    ", ((num[ra[r]] - num[ra[l]]) % mod + mod) % mod);
      }
    }
    
    
  • 相关阅读:
    rac node1节点侦听地址为127.0.0.1问题
    rac crs节点故障(未解决)
    杂牌设备网络接入引起交换机引起端口翻转的故障
    Impdp导出前准备工作以及主要命令
    查询dba_tables视图tablespaces_name为空的问题
    Expdp导出前准备工作以及主要命令
    带参数的方法解析
    类的无参方法,注释
    类和对象的解析
    二重循环的解析
  • 原文地址:https://www.cnblogs.com/luoshuitianyi/p/10500193.html
Copyright © 2011-2022 走看看