某大学每年都会有一次 \(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\) , 一个质数)的值。
第一行三个数\(n,m,q\),含义如题所述。
接下来\(n\)行,其中第\(i\)行有一个长度为\(m\)的二进制串,左边是最高位,表示\(a_i\)。
接下来\(q\)行,其中第\(i\)行有一个长度为\(m\)的二进制串,左边是最高位,表示\(r_i\)。
输出\(q\)行,每行一个数,其中第\(i\)行表示对应于\(r_i\)的答案。
5 5 1
01110
11011
10000
01010
00100
00100
6
10 10 3
0100011011
0110100101
1100010100
0111000110
1100011110
0001110100
0001101110
0110100001
1110001010
0010011101
0110011111
1101001010
0010001001
69
0
5
有以下且仅有以下六个运算式的值是\(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}\)
对于 \(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\)
基数排序可能算一个???
这个题看起来超级吓人 \(......\) 难度看起来确实挺大的样子。
但是我们应该冷静思考,先看看数据范围,\(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\) ,学一下吧,挺简单的。
#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);
}
}
原文:https://www.cnblogs.com/luoshuitianyi/p/10500193.html