[BZOJ5285][HNOI2018]寻宝游戏-思维

寻宝游戏

Description

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

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

很快,在最新的一期的Voo Doo杂志上,你发现了 q 个长度也为 m 的二进制数 r1,r2,...,rq

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

保持数组 a1,a2,...,an 的元素顺序不变,你可以在它们之间插入 (按位与运算)或者 (按位或运算)。例如: 1101100111=00011 1101100111=11111

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

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

Input

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

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

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

Output

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

Sample Input

5 3 0
1 2 3 4 5
3 5
5 0
1 4

Sample Output

5
7
6
7

HINT


考场上不会倒推做法,暴力还超时了,正解想到一半就想不动了(而且也不知道那是正解)……
咱是真的菜QAQ


思路:
首先很显然需要按位考虑。

观察每一位,可以发现, |0 和&1是毫无意义的。
若某一位的值为 1 ,则需要满足最后一个|1出现在最后一个&0的后面。为 0 则相反。

考虑把所有运算符写成一个01串,令所有|运算为0,&运算为 1 ,第i位上的 0/1 代表第 i 个数前的运算符。
同样考虑蒋所有数某一位的0/1提取成一个01串,观察如何才能使最终结果为 1
可以发现,把最右端看成最高位,从高到低比较同为0 1 无影响,运算符为1而值为 0 则不合法,否则合法。

于是可以发现,能使结果为1的运算符串为所有字典序小于当前位构成的01串的01串。

考虑对于每一位形成的01串排序。
对于每次询问,首先观察是否合法,即是否有合法的运算符集合对于询问串的每一位均满足要求。
即,对于询问串,所有为 0 的位在原序列中对应位构成的01序列的排名,小于所有为1的位对应01串的排名。
然后计算答案,可以发现是小于最小的为 1 的位对应01串的合法运算符串数目,减去小于最大的为0的位对应01串的合法运算符串数目。

于是就做完了~

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N=1009;
const int M=5009;
const int md=1e9+7;

int n,m,q;
int rk[M],sum[M],tmp[M],pows[N];
char s[M];

int main()
{
    scanf("%d%d%d",&n,&m,&q);

    for(int i=1;i<=m;i++)
        rk[i]=i;
    pows[0]=1;
    for(int i=1;i<=n;i++)
        pows[i]=pows[i-1]*2ll%md;

    for(int i=1;i<=n;i++)
    {
        int cnt[]={0,0};
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
        {
            sum[j]=(sum[j]+(ll)(s[j]-'0')*pows[i-1])%md;
            cnt[s[j]-'0']++;
        }
        cnt[1]+=cnt[0];
        for(int j=m;j;j--)
            tmp[cnt[s[rk[j]]-'0']--]=rk[j];
        swap(rk,tmp);
    }

    reverse(rk+1,rk+m+1);

    for(int i=1;i<=q;i++)
    {
        scanf("%s",s+1);
        int lst1=0,fst0=m+1;
        for(int j=m;j>=1 && lst1==0;j--)
            if(s[rk[j]]-'0')
                lst1=j;
        for(int j=1;j<=m && fst0==m+1;j++)
            if(!(s[rk[j]]-'0'))
                fst0=j;
        if(lst1>fst0)
        {
            puts("0");
            continue;
        }
        int c0=lst1>=1?sum[rk[lst1]]:pows[n];
        int c1=fst0<=m?sum[rk[fst0]]:0;
        printf("%d\n",(c0-c1+md)%md);
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值