洛谷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}$ 。例如
于是我们就把问题转化为了比大小。我们把 $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;
}
参考文献: