【HNOI 2018】寻宝游戏
【HNOI 2018】尋寶游戲
Problem
Description
某大學每年都會有一次 \(Mystery\ Hunt\) 的活動,玩家需要根據設置的線索解謎,找到寶藏的位置,前一年獲勝的隊伍可以獲得這一年出題的機會。
作為新生的你,對這個活動非常感興趣。你每天都要從西向東經過教學樓一條很長的走廊,這條走廊是如此的長,以至于它被人戲稱為 \(Infinite\ Corridor\) 。一次,你經過這條走廊的時候,注意到在走廊的墻壁上隱藏著 \(n\) 個等長的二進制的數字,長度均為 \(m\) 。你從西向東將這些數字記錄了下來,形成一個含有 \(n\) 個數的二進制數組 \(a_1, a_?2 \cdots, a_?n\) 。
很快,在最新的一期 \(Voo\ Doo\) 雜志上,你發現了 \(q\) 個長度也為 \(m\) 的二進制串 \(r_1, r_?2, \cdots, r_?q\) 。
聰明的你很快發現了這些數字的含義。
保持數組 \(a_1, a_?2 \cdots, a_n\) 的元素順序不變,你可以在它們之間插入 \(\land\)(按位與運算)或者 \(\vee\)(按位或運算)兩種二進制運算符。例如: \(11011 \land 00111 = 00011,11011 \vee 00111 = 11111\) 。
你需要插入恰好 \(n\) 個運算符,相鄰兩個數之間恰好一個,在第一個數的左邊還有一個。如果我們在第一個運算符的左邊補入一個 \(0\) ,這就形成了一個運算式,我們可以計算它的值。與往常一樣,運算順序是從左往右。有趣的是,出題人已經告訴你這個值的可能的集合—— \(Voo Doo\) 雜志里的那一些二進制數 \(r_1, r_?2, \cdots, r_q\) ,而解謎的方法,就是對 \(r_1, r_?2, \cdots, r_q\) 中的每一個值 \(r_i\) ,分別計算出有多少種方法填入這 \(\mathbf{n}\) 個運算符,使得這個運算式的值是 \(r_i\) 。
然而,\(Infinite\ Corridor\) 真的很長,這意味著數據范圍可能非常大。因此,答案也可能非常大,但是你發現由于謎題的特殊性,你只需要求答案模\(1000000007(10 ^ 9 + 7\) , 一個質數)的值。
Input Format
第一行三個數\(n,m,q\),含義如題所述。
接下來\(n\)行,其中第\(i\)行有一個長度為\(m\)的二進制串,左邊是最高位,表示\(a_i\)。
接下來\(q\)行,其中第\(i\)行有一個長度為\(m\)的二進制串,左邊是最高位,表示\(r_i\)。
Output Format
輸出\(q\)行,每行一個數,其中第\(i\)行表示對應于\(r_i\)的答案。
Sample
Input 1
5 5 1 01110 11011 10000 01010 00100 00100Output 1
6Input 2
10 10 3 0100011011 0110100101 1100010100 0111000110 1100011110 0001110100 0001101110 0110100001 1110001010 0010011101 0110011111 1101001010 0010001001Output2
69 0 5Explanation
Explanation for Input 1
有以下且僅有以下六個運算式的值是\(00100_{2}\):(下標2表示被標識的數是二進制數)
\(0 \land \ 01110_{2} \land \ 11011_{2} \vee {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)
\(0 \vee \ 01110_{2}\ {\vee 11011}_{2}\ \land {\ 10000}_{2}{\ \ \land 01010}_{2}{\ \vee 00100}_{2}\)
\(0 \land \ 01110_{2}\ {\vee 11011}_{2} \land {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)
\(0 \vee \ 01110_{2} \land \ 11011_{2} \land {\ 10000}_{2}{\land \ 01010}_{2}{\ \vee 00100}_{2}\)
\(0 \land \ 01110_{2}\ {\land 11011}_{2} \land {\ 10000}_{2}{\ \land 01010}_{2}{\ \vee 00100}_{2}\)
\(0 \vee \ 01110_{2}\ \vee 11011_{2} \vee {\ 10000}_{2}{\ \ \vee 01010}_{2}{\ \land 00100}_{2}\)
Range
對于 \(10\%\) 的數據,\(n \leq 20, m \leq 30, \ q = 1\)
對于另外 \(20\%\) 的數據,\(n \leq 1000, m \leq 16\)
對于另外 \(40\%\) 的數據,\(n \leq 500, m \leq 1000\)
對于 \(100\%\) 的數據,\(1 \leq n \leq 1000, \ 1 \leq m \leq 5000, \ 1 \leq q \leq 1000\)
Algorithm
基數排序可能算一個???
Mentality
這個題看起來超級嚇人 \(......\) 難度看起來確實挺大的樣子。
但是我們應該冷靜思考,先看看數據范圍,\(10^3\) 左右,這大概就只有 \(O(nm)\) 的復雜度才能過了,最多帶點小常數,連 \(log\) 都帶不起。
辣么怎么辦呢?冷靜分析的話,我們決定單獨來按位思考,想想每個二進制數的某一位經過運算的結果。
首先,我們發現每一位的運算符只有四種情況:
\(\&0,\&1,|0,|1\)
而我們需要學會發現這四種情況里沒啥用的情況,那就是 \(\&1\) 和 \(|0\) 這兩個運算,它們對于運算結果沒有任何影響。那么有影響的就只剩下了兩種情況:當前位為 \(1\) 的時候,我們插入 \(|\) 符號將會使得結果必定為 \(1\) ,否則結果不變;當前位為 \(0\) 的時候,我們插入 \(\&\) 符號會使得結果必定為 \(0\) 。否則結果不變。
那么我們需要做的就變得很簡單了:如果詢問中這一位為 \(1\) ,那么我們必需確保最后一個有效的操作 (\(\&0\) 和 \(|1\)) 為 \(|1\) ,如果這一位為 \(0\) ,則必須確保最后一個有效操作為 \(\&0\) 或者沒有有效操作 (初始值為 \(0\)) 。
接著就是一個異常巧妙的轉化了:
由于我們發現 \(\&1\) 和 \(|0\) 這兩個東西對結果沒有任何影響,那么我們開開腦洞:我們認為 \(\&\) 和 \(1\) 是等價的,\(|\) 和 \(0\) 是等價的!
然后腦洞不要停,我們將當前位每個數前插入運算符構成一個 \(01\) 串來看,設這個串為 \(opt\) ,其中的 \(\&\) 運算代表的值就直接設為 \(1\) ,\(|\) 運算代表的值直接設為 \(0\) 。那么我們發現,將 \(opt\) 與當前位的數構成的 \(01\) 串對其后,如果數為 \(1\) ,而 \(opt\) 的相同位置為 \(1\) 的話,這一位上的二進制值就是相等的,這符合我們等價的腦洞。
接著,我們會異常驚喜地發現一件事情,根據之前的說法,如果我們要使運算的結果為 \(1\) ,那么最后一個有效的操作必須為 \(|1\) ,則對于最后一個有效操作的位置往后,都必須是等價操作 (也即無效操作) 。
而最后一個要求結果為 \(1\) 的有效操作位置,我們必須填入 \(|\) ,也就是在 \(opt\) 串的相同位置,我們的值為 \(0\) ,而我們此位置往后的位置又全部相等!
想到了什么嗎?對!如果我們將 \(n\) 個數的當前位提出來,從后往前構成一個二進制數的話,我們必須大于當前 \(opt\) 串代表的二進制數!
舉個例子:
這是 \(n\) 個數的當前位:\(1010111\)
若要使結果為 \(0\) ,假設我們最后一個有效操作在第 \(5\) 位,那么最后兩位的運算操作都必須為 \(\&\) 運算,也就是 \(1\) ,那么我們的操作串如下,\(.\) 代表既可以填 \(1\) 有可以填 \(0\) 。
\(opt:\ ....011\)
當前位倒過來之后為:\(1110101\)
操作串倒過來之后為:\(opt:\ 110....\)
\(opt<\) \(n\) 個數當前位構成的串
總結陳詞,我們設 \(b_i\) 為 \(n\) 個數的第 \(i\) 位提出來再倒過來組成的二進制數,那么若 \(r_i\) 的第 \(j\) 位為 \(1\) ,我們必須保證我們的操作串 \(opt< b_j\) ,而我們已經證明過了,這是必然要求,那么換而言之,如果 \(opt\ge b_j\) ,這一位的結果就必定為 \(0\) 。
于是我們的題目變成了好玩的比大小游戲,我們只需要根據 \(m\) 個和 \(opt\) 有關的不等式得出結果就好了 \(hhhg\) 。
那這題的做法出來了!
先預處理出 \(b\) 數組并排序。
對于當前詢問,我們找到滿足 \(r_i=0\) 的最大 \(b_i\) ,設當前 \(i\) 為 \(L\) ,滿足 \(r_i=1\) 的最小 \(b_i\) ,設當前 \(i\) 為 \(R\) ,那么最后的答案肯定就是 \(b_R-b_L\) 啊 \(hhhhh\) 。
當然注意當 \(L>R\) 輸出 \(0\) 。
您問我怎么排序?當然是基數排序啊!常數只有 \(2\) 的 \(O(nm)\) 排序算法。
但是如果您不知道基數排序我就沒辦法了 \(QwQ\) ,學一下吧,挺簡單的。
Code
#include<iostream> #include<cstdio> using namespace std; const int mod=1e9+7; int n,m,Q,tag[2],ra[5002],rb[5002],num[5001],mi[1002],t[5002]; char s[5002],q[5002]; void Mod(int &x){x=(x%mod+mod)%mod;} int main() {cin>>n>>m>>Q;mi[1]=1;for(int i=2;i<=n+1;i++)mi[i]=(mi[i-1]<<1)%mod;//預處理一下冪for(int i=1;i<=m;i++)ra[i]=i;//排名for(int i=1;i<=n;i++){scanf("%s",s+1);tag[0]=0,tag[1]=m;//基排的桶for(int j=1;j<=m;j++){Mod(num[j]+=s[j]=='1'?mi[i]:0);//處理 b 數組的值if(s[j]=='0')tag[0]++;}for(int j=m;j>=1;j--)rb[tag[s[ra[j]]-'0']--]=ra[j];//獲取下一輪排名swap(ra,rb);//更新排名}ra[m+1]=m+1;num[m+1]=mi[n+1];//因為如果要求的結果中沒有 1 ,那么操作串大小上限就是 2^n-1 了。while(Q--){scanf("%s",q+1);int l=0,r=m+1;for(int i=1;i<=m;i++)if(q[ra[i]]=='1'){r=i;break;}//找到最小的for(int i=m;i>=1;i--)if(q[ra[i]]=='0'){l=i;break;}//找到最大的if(l>r)cout<<"0\n";else printf("%d\n",((num[ra[r]]-num[ra[l]])%mod+mod)%mod);} } posted @ 2019-03-09 12:07 洛水·錦依衛 閱讀(...) 評論(...) 編輯 收藏總結
以上是生活随笔為你收集整理的【HNOI 2018】寻宝游戏的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神经网络和有限元方法
- 下一篇: 英语foteball足球foteball