【HNOI 2018】寻宝游戏

【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<iostream>
#include<cstdio>
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 洛水·锦依卫 阅读( ...) 评论( ...) 编辑 收藏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值