嘘~ 正在从服务器偷取页面 . . .

洛谷P4424 [HNOI/AHOI2018] 寻宝游戏 题解


洛谷P4424 [HNOI/AHOI2018] 寻宝游戏 题解

题目链接:P4424 [HNOI/AHOI2018] 寻宝游戏

题意

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

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

很快,在最新的一期的 Voo Doo 杂志上,你发现了 \(q\) 个长度也为 \(m\) 的二进制数 \(r_1,r_2,...,r_q\)

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

保持数组 \(a_1,a_2,...,a_n\) 的元素顺序不变,你可以在它们之间插入 \(\land\)(按位与运算)或者 \(\lor\)(按位或运算)。例如:\(11011\land 00111=00011\)\(11011\lor 00111=11111\)

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

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

输入格式

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

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

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

输出格式

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

数据范围

\(1\leq n\leq 1000,1\leq m\leq 5000,1\leq q\leq 1000\)

依次考虑每一个数字 \(i\) 的每一位 \(j\) 。方便起见,我们定义最右侧的那一位是最高位。

注意到 \(x\land 0=0\)\(x\lor 1=1\) 这两个操作,无论前面是什么结果,到这里就肯定是 \(0\)\(1\)

注意到 \(x \lor 0=x\)\(x \land 1=x\) 这两个操作,无论前面是什么结果,到这里都不会变。

要使得第 \(j\) 位的运算结果是 \(1\) ,因此最后一个 \(x\lor 1\) 的位置要在 \(x\land 0\) 的前面。

考虑记操作序列为 \(\rm op\) ,其中 \(\mathrm{op}(i):= [\textbf{option}=\, \land]\) 。也就是把 \(\lor\) 记为 \(0\)\(\land\) 记为 \(1\)

然后把第 \(j\) 位的数提出来作为一个长度为 \(n\) 的二进制数,记为序列 \(x_j\)

那么根据刚才我们定义的序列,这个问题就转化为了 \(x_j > \mathrm{op}\) 。例如 \[ \begin{aligned} & x_j=1011, \mathrm{op}=0011, x_j>\mathrm{op} \\[6pt]& 0 \vee 1 \vee 0 \wedge 1 \wedge 1=1 \\[6pt]& x_j=1011, \mathrm{op}=1011, x_j \leq \mathrm{op} \\[6pt]& 0 \wedge 1 \vee 0 \wedge 1 \wedge 1=0 \end{aligned} \] 于是我们就把问题转化为了比大小。我们把 \(x_j\) 按二进制数的值排序

然后每次判断 \(x_j\)\(\rm op\) 的大小关系,如果有解可以得到 \(x_l \le \mathrm{op} < x_r\) ,答案就是 \(x_r - x_l\)

时间复杂度 \(\mathcal{O}((n+q)m)\) ,排序可以用计数排序。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
const int mod = 1e9 + 7;
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
void add(int &x, int y) { (x += y) >= mod ? x -= mod : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(5e3 + 15))

char s[N];
int pw2[N], a[N], b[N], t[N], cnt[2], v[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n, m, q; cin >> n >> m >> q; pw2[1] = 1;
    rep(i, 2, n + 1) pw2[i] = pw2[i - 1] * 2 % mod;
    rep(i, 1, m) a[i] = i;
    rep(i, 1, n)
    {
        cin >> (s + 1); cnt[0] = 0; cnt[1] = m;
        rep(j, 1, m) { if(s[j] == '1') add(v[j], pw2[i]); else ++cnt[0]; }
        Rep(j, m, 1) { b[cnt[s[a[j]] - '0']--] = a[j]; } swap(a, b);
    }
    rep(i, 1, m) t[i] = v[a[i]] % mod;
    int mx, mn; t[m + 1] = pw2[n + 1];
    rep(l, 1, q)
    {
        cin >> (s + 1); mx = m + 1, mn = 0;
        Rep(i, m, 1) if(s[a[i]] == '0') { mn = i; break; }
        rep(i, 1, m) if(s[a[i]] == '1') { mx = i; break; }
        if(mx < mn) cout << "0\n";
        else { cout << (t[mx] + mod - t[mn]) % mod << '\n';}
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/n1y3maag


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录