1. 程式人生 > >[BZOJ5285][HNOI2018]尋寶遊戲-思維

[BZOJ5285][HNOI2018]尋寶遊戲-思維

尋寶遊戲

Description

某大學每年都會有一次Mystery Hunt的活動,玩家需要根據設定的線索解謎,找到寶藏的位置,前一年獲勝的隊伍可以獲得這一年出題的機會。

作為新生的你,對這個活動非常感興趣。你每天都要從西向東經過教學樓一條很長的走廊,這條走廊是如此的長,以至於它被人戲稱為infinite corridor。一次,你經過這條走廊時注意到在走廊的牆壁上隱藏著 n 個等長的二進位制的數字,長度均為 m 。你從西向東將這些數字記錄了下來,形成一個含有 n 個數的二進位制陣列 a1,a2,...,an

很快,在最新的一期的Voo Doo雜誌上,你發現了 q 個長度也為 m

的二進位制數 r1,r2,...,rq

聰明的你很快發現了這些數字的含義。

保持陣列 a1,a2,...,an 的元素順序不變,你可以在它們之間插入 (按位與運算)或者 (按位或運算)。例如: 1101100111=000111101100111=11111

你需要插入 n 個運算子,相鄰兩個數之前恰好一個,在第一個數的左邊還有一個。如果我們在第一個運算子的左邊補入一個0,這就形成了一個運算式,我們可以計算它的值。與往常一樣,運算順序是從左到右。有趣的是,出題人已經告訴你這個值的可能的集合——Voo Doo雜誌裡的那些二進位制數 r1,r2,...,rq ,而解謎的方法,就是對 r

1,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
可以發現,把最右端看成最高位,從高到低比較同為01無影響,運算子為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;
}