【HNOI2018寻宝游戏】

寻宝游戏

题意:

给定\(n\)个长度为\(m\)\(01\)

现在允许你在对\(0\)依次“按位与”或者“按位或”上这些\(01\)

\(q\)组询问,每次询问有多少种方法填入“按位与”或者“按位或”使得最终结果为询问的\(01\)

\(Sol:\)

\(1.\)

\[0~|~1~=~1~,\quad 1~|~1~=~1\]

\[0~\&~0=~0,\quad 1~\&~0~=~0\]

于是有对于一个\(1\)如果其前面填入的为\(~|~\),那么无论如何答案都是\(1\)

对于一个\(0\)如果其前面填入的为\(\&\),那么无论如何答案都是\(0\)

\(2.\)

\[1~|~0~=~1~,\quad 0~|~0~=~0\]

\[1~\&~1=~1,\quad 0~\&~1~=~0\]

于是有对于一个\(1\)如果其前面为\(\&\)那么答案不会改变

对于一个\(0\)如果其前面为\(~|~\)那么答案也不会改变

\(3.\)

我们先考虑最基础的问题,假设所有的\(0/1\)串都只有\(1\)

我们从后往前考虑所有的\(0/1\),可以将其变成一个序列

由于两两之间也有一个操作,于是把操作单独提出来也是一个序列

规定它为操作序列,我们尝试把操作序列转成一个\(0/1\)序列

\(~|~\)\(0\)\(\&\)\(1\)

于是对于一个\(0/1\)序列\(10010\)等,其运算结果为\(1\)当且仅当其\(>\)操作序列,否则其为\(0\)

这是因为对于操作序列,如果偏高位与\(0/1\)序列全部相等,则为情况\(2\)运算结果不变,否则如果存在一个\(1\)且操作序列对应位为\(0\)则为情况\(1\),运算结果为\(1\)

于是对于一位的情况,我们成功将这道题转化成了一道比大小的题目了\(qwq\)

对于每个\(0/1\)串若其有\(m\)位我们就对其每一位单独考虑

若结果要求当且位为\(1\)则表明操作序列\(<\)当且位,否则表示操作序列\(\ge\)当前位

于是我们将所有的\(0/1\)串提前排个序,问题就只需要取\(0\)中最大值\(x\)\(1\)中最小值\(y\)\(y-x\)即方案数

一点细节

注意到我们最后要反向,还要对每一位单独处理

于是好像有我们读入的就是待排序的最低位,次低位...最高位

于是好像可以边读入边基排...

复杂度\(O(nm+qm)\)

#include
using namespace std;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define drep( i, s, t ) for( register int i = t; i >= s; -- i )
#define int long long
#define re register
int read() {
    char cc = getchar(); int cn = 0, flus = 1;
    while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
    while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
    return cn * flus;
}
const int P = 1000000007 ; 
const int N = 1000 + 5 ;
const int M = 5000 + 5 ; 
int n, m, q, lk[M], rk[M], b[M][N], a[M], Ans[M] ; 
char s[M] ;
int Get( int x, int y ) {
    return ( Ans[y] - Ans[x] + P ) % P ; 
}
signed main()
{
    n = read(), m = read(), q = read() ; 
    rep( i, 1, m ) rk[i] = i ; 
    rep( i, 1, n ) {
        scanf("%s", s + 1 ) ; int rs = 0 ; 
        rep( j, 1, m ) a[j] = s[j] - '0', b[j][i] = a[j] ; 
        rep( j, 1, m ) if( a[rk[j]] == 0 ) lk[++ rs] = rk[j] ;
        rep( j, 1, m ) if( a[rk[j]] == 1 ) lk[++ rs] = rk[j] ;
        rep( j, 1, m ) rk[j] = lk[j] ; 
    }
    rep( j, 1, m ) drep( i, 1, n ) Ans[j] = ( Ans[j] * 2ll + b[j][i] ) % P ;
    rep( j, 1, n ) Ans[m + 1] = ( Ans[m + 1] * 2ll + 1 ) % P ; 
    rk[m + 1] = m + 1 ; Ans[m + 1] += 1 ; 
    while( q -- ) {
        scanf("%s", s + 1 ) ; int Lk = 0, Rk = m + 1 ; 
        rep( j, 1, m ) if( s[rk[j]] == '1' ) { Rk = j ; break ; }
        drep( j, 1, m ) if( s[rk[j]] == '0' ) { Lk = j ; break ; } 
        printf("%lld\n", ( Rk < Lk ) ? 0 : Get( rk[Lk], rk[Rk] ) ) ;
    } 
    return 0;
}

你可能感兴趣的:(【HNOI2018寻宝游戏】)