2019.03.01【HNOI2018/AHOI2018】【BZOJ5285】【洛谷P4424】寻宝游戏(二进制拆分)(数学推理)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/88069489

洛谷传送门

BZOJ传送门


解析:

先%出题人myy

这道题的转化方向二进制还是可以想到的,但是我没转化出来。。

滚去orz了题解。

分位考虑,一共只有四种操作, 1 , & 0 , & 1 , 0 |1,\&0,\&1,|0 ,其中前两种是强行改变,另外两种是屁用没有。

对于某一位来说,要令它为 1 1 ,就是要求最后一个 1 |1 操作在最后一个 & 0 \&0 操作之后。

我们把位运算序列转化成一个长度为 n n 的二进制串,把后面设置成高位。

| 运算设置成 0 0 & \& 运算设置成 1 1

将所有原来的二进制数的这一位提取出来成一个 01 01 串,同样将最后视作高位。

然后我们滚去高位看,直到这两个串相同的前缀。显然只有两种不会改变结果的操作 & 1 , 0 \&1,|0 ,直到遇到不同的部分。我们发现遇到第一位不同的操作就可以判断当前操作序列是否合法。

比如我们现在需要令当前位为 1 1 ,则在高位(即操作序列的后面)出现了 & 0 \&0 操作就肯定非法,反之,如果是 1 |1 操作,不管低位(即操作序列的前缀)什么样都肯定合法。

我们发现这个操作实际上就是比较二进制数的大小。

将所有位上的字符串排一个字典序,答案就是最小的结果为 1 1 的二进制-最大的结果为 0 0 的二进制。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define cs const

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}

int n,m,q;
int Pow2[5003];
char ss[5003];
int c[2];
int t[5003],s[5003];
int arr[5003<<1],*a=arr,*b=arr+5003;

signed main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int re i=Pow2[0]=1;i<=n+1;++i)Pow2[i]=add(Pow2[i-1],Pow2[i-1]);
    for(int re i=1;i<=m;++i)a[i]=i;
    for(int re i=1;i<=n;++i){
        c[0]=0,c[1]=m;scanf("%s",ss+1);
        for(int re j=1;j<=m;++j)ss[j]^48?s[j]=add(s[j],Pow2[i-1]):++c[0];
        for(int re j=m;j;--j)b[c[ss[a[j]]^48]--]=a[j];swap(a,b);
    }
    for(int re i=1;i<=m;++i)t[i]=s[a[i]];
    t[m+1]=Pow2[n];
    int d,u;
    while(q--){
        scanf("%s",ss+1);
        for(d=m;d>=1;--d)if(ss[a[d]]=='0')break;
        for(u=1;u<=m;++u)if(ss[a[u]]=='1')break;
        cout<<(u<d?0:dec(t[u],t[d]))<<"\n";
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/88069489