[LOJ2494][AHOI/HNOI2018]寻宝游戏

泪湿孤枕 提交于 2019-12-11 03:04:07

题目描述

 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会。作为新生的你,对这个活动非常感兴趣。你每天都要从西向东经过教学楼一条很长的走廊,这条走廊是如此的长,以至于它被人戏称为 infinite corridor。一次,你经过这条走廊的时候,注意到在走廊的墙壁上隐藏着𝑛个等长的二进制的数字,长度均为𝑚。你从西向东将这些数字记录了下来,形成一个含有𝑛个数的二进制数组 a1,a2,…,an。很快,在最新的一期 Voo Doo 杂志上,你发现了𝑞个长度也为𝑚的二进制串 r1,r2,…,rq。聪明的你很快发现了这些数字的含义。保持数组 a1,a2,…,an 的元素顺序不变,你可以在它们之间插入∧(按位与运算)或者∨(按位或运算)两种二进制运算符。例如:11011∧00111=00011,11011∨00111=11111。你需要插入恰好𝑛个运算符,相邻两个数之间恰好一个,在第一个数的左边还有一个。如果我们在第一个运算符的左边补入一个 0,这就形成了一个运算式,我们可以计算它的值。与往常一样,运算顺序是从左往右。有趣的是,出题人已经告诉你这个值的可能的集合——Voo Doo 杂志里的那一些二进制数 r1,r2,…,rq,而解谜的方法,就是对 r1,r2,…,rq 中的每一个值 ri,分别计算出有多少种方法填入这𝒏个运算符,使得这个运算式的值是 ri。
 然而,infinite corridor 真的很长,这意味着数据范围可能非常大。因此,答案也可能非常大,但是你发现由于谜题的特殊性,你只需要求答案模1000000007(109 + 7,一个质数)的值。

Sample Input

10 10 3
0100011011
0110100101
1100010100
0111000110
1100011110
0001110100
0001101110
0110100001
1110001010
0010011101
0110011111
1101001010
0010001001

Sample Output

69
0
5

数据范围

n<=1000 m<=5000 q<=1000

思路

 首先观察发现|1会使结果变成1,&0会使结果变成0。而|0和&1没有任何的作用,可以忽略。数据中每一列都是独立的,与其它列无关。又因为操作只有两种,数也是二进制数,而且每一行对应一个操作,可以想到把操作也变为一个二进制数(令&=1,|=0)来和每一列的二进制数比较大小。对于每一列来说,要是最后要求结果为1,那么它的最后一个有效操作一定是|1;如果要求结果为0,那么可以最后一个有效操作为&0或没有有效操作(初始为0).于是,把每一列变成一个倒序排列的二进制数。根据定义,可推出:若要求结果为0,那么操作序列数一定>=当列二进制数;若要求为1,操作序列数一定小于当列二进制数。所以,对于每一个询问,就是求出要求结果为1,二进制数最小的列;以及要求结果为0,二进制数最大的列。可以对每列构成的二叉树进行基数排序。因为只有0,1,所以常数很小。最终复杂度为O(nm)。
***因为要求结果为1时,得到的二进制数是取不到的,相当于是一个左闭右开的区间。所以在定哨兵m+1时最后的值要加1。

Code

#include<cstdio>
#include<cstring>
using namespace std;
const int mod=1000000007;
int n,m,q,l,r;
char c[5007];
int s[5007][1007],rk[5007],b[2][5007],cnt[2];
void b_sort(){
    for (int i=0; i<=m+1; i++) rk[i]=i;
    for (int i=n; i>0; i--) {
        cnt[0]=cnt[1]=0;
        for (int j=1; j<=m; j++) 
            if (s[rk[j]][i]==0) b[0][++cnt[0]]=rk[j];
            else b[1][++cnt[1]]=rk[j]; 
        for (int j=1; j<=cnt[0]; j++) rk[j]=b[0][j];
        for (int j=1; j<=cnt[1]; j++) rk[j+cnt[0]]=b[1][j];
    }
}
long long ans() {
    int x=rk[l],y=rk[r];
    long long res1=0,res2=0;
    for (int i=1; i<=n; i++) {
        res1=res1*2%mod;
        res1=(res1+s[x][i])%mod;
        res2=res2*2%mod;
        res2=(res2+s[y][i])%mod;
    }  
    if (y==m+1) res2++;
    return (((res2-res1)%mod)+mod)%mod;
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1; i<=n; i++) {
        scanf("%s",c+1);
        for (int j=1; j<=m; j++) s[j][n-i+1]=c[j]-'0';  
    }
    for (int i=1; i<=n; i++) s[m+1][i]=1;
    b_sort();
    while (q-->0) {
        scanf("%s",c+1);
        l=0; r=m+1;
        for (int i=1; i<=m; i++) if (c[rk[i]]=='1') {r=i; break;}
        for (int i=m; i>0; i--) if (c[rk[i]]=='0') {l=i; break;}
        if (l>r) printf("0\n");
        else printf("%lld\n",ans());
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!