“在信息學競賽中,有這樣一類問題:求給定區間中,滿足給定條件的某個D 進制數或此類數的數量。所求的限定條件往往與數位有關,例如數位之和、指定數碼個數、數的大小順序分組等等。題目給定的區間往往很大,無法採用樸素的方法求解。此時,我們就需要利用數位的性質, 設計log(n)級別複雜度的算法。 解決這類問題最基本的思想就是 “逐位確定”的方法。下面就讓我們通過幾道例題來具體瞭解一下這類問題及其思考方法。”——09集訓隊論文《淺談數位類統計問題》by 劉聰
以前碰到過一些數位統計的題目,往往這類題目比較讓人惱火,做這類題的時候總是有一種力不從心的感覺,如果設計出來的算法不漂亮那將意味着有衆多的細節需要一一特判之,比如去年的zjoi就有一道統計區間[l,r]中的數的每一位上0~9出現了多少次,結果這道題我整整做了兩個小時才把思路理順清楚。
這篇論文向我介紹了一種非常形象的的思考及算法方向,那就是數形結合。數字往往好深入分析但是太過於抽象,形則直觀明瞭但是難以究其根源,很好的結合兩者將會使得思考複雜度大大降低。
這篇論文一共介紹了5到例題,ly還在poj上也找了一道數位統計題,至今還剩論文中的最後一題沒有AC==。
1.Amount of degrees (ural 1057)
題目大意:
求給定區間[X,Y]中滿足下列條件的整數個數:這個數恰好等於K 個互不相等的 B的整
數次冪之和。例如,設X=15,Y=20,K=2,B=2,則有且僅有下列三個數滿足題意:
17 = 10001
18 = 10010
20 = 10100
輸入:第一行包含兩個整數X 和 Y。接下來兩行包含整數 K 和 B。
輸出:只包含一個整數,表示滿足條件的數的個數。
數據規模:1 ≤ X ≤ Y ≤ 231−1,1 ≤ K ≤ 20, 2 ≤ B ≤ 10。
分析:
對於B>2的所有詢問都可以轉化成B=2,由淺入深的先考慮B=2的情況。
因爲滿足區間減法,所以問題就轉化爲統計[0,n]中的2進制數中1的個數爲k的數的個數。
再簡單一點,如果n是2x-1的話答案是什麼?就是C(k,x)。
那麼能不能把[0,n]拆成若干個2x-1呢?可以的,如果我們把這裏面的數字看成一顆2叉樹,那麼這顆二叉樹肯定是由不超過logN個滿二叉樹構成的,所以我們可以把一個問題拆分成logN個問題分別求解。
2.Sorted bit sequence (spoj 1182)
題目大意
求給定區間[L,R]中第k大的數,兩個數的大小關係被定義爲:對應的二進制數中的1的個數爲第一關鍵字,大小爲第二關鍵字。
分析:
首先直接枚舉第K大的數的1的個數,問題轉化爲求區間[L,R]中1的個數爲X的數的個數,見第一題。
枚舉出第K大的數的一的個數之後,在二分答案即可,同第一題。
3.Apocalypse Someday(poj3208)
題目大意
求第K大的包含666的數字
分析:依然是考慮逐位統計,預處理出F[I,J]表示還有i位沒有確定,之前有連續J個6的數有多少個,枚舉每一位即可。
4.Sequence (spoj 2319)
題目大意
給定所有K 位二進制數:0,1,…,2K-1。你需要將它們分成恰好 M組,每組都是原序列中連續的一些數。設Si(1 ≤ i ≤ M)表示第 i組中所有數的二進制表示中 1 的個數,S等於所有Si中的最大值。你的任務是令S最小。
數據規模:1 ≤ K ≤ 100, 1 ≤ M ≤ 100, M ≤ 2K。
分析
最大值最小,很明顯的二分。
首先二分答案,然後貪心的求出最多可以分爲幾組。
剩下的問題就是怎麼樣貪心了,我們可以每次選一個點爲起點然後再找到一個最大的終點使得所有數1的個數≤S。
5.Tickets (sgu 390)
題目大意:
有一位售票員給乘客售票。對於每位乘客,他會賣出多張連續的票,直到已賣出的票的編號的數位之和不小於給定的正數k。 然後他會按照相同的規則給下一位乘客售票。 初始時,售票員持有的票的編號是從L到R的連續整數。 請你求出, 售票員可以售票給多少位乘客。
輸入:三個整數L,R和k。
輸出:輸出一個整數,表示問題的答案。
數據規模:1 ≤ L ≤ R ≤ 1018,1 ≤ k ≤ 1000。
分析
本題與上題有些類似,也是對區間內的整數進行連續的分組。與上題不同的是,本題中k 的值較小,也就表示分組數量非常巨大,不可能求出每一個分組。 讓我們同之前一樣,從特殊情況開始考慮。假設給定的區間是[1..10h-1](也就是一棵完整的高度爲h的完全十叉樹) ,考慮如何由子問題組合出原問題。這裏我們遇到的問題是,兩棵子樹間的“合併”較難處理:前一棵子樹的最後一個區間未必是滿的。亦即,一棵子樹的頭幾個元素會併入前一棵子樹最後的分組當中。爲了解決這個問題,我們引入一維,記錄每棵子樹之前有多少空間。因此就得到了下面的遞推算法:
設f[h,sum,rem]表示對於高度爲h的完全十叉樹,在這個區間內的每個數需要累加的數字和爲sum,在這個區間之前有rem的空間,所能得出的分組數量。f的返回值需要記錄兩個數:f[h,sum,rem].a記錄分組數量,f[h,sum,rem].b 記錄其最後一個小組剩餘的空間,以便於與下一個子樹合併。f可由其十個子樹推出:
for i:=0 to 9 do f[h,sum,rem]:=merge(f[h,sum,rem],f[h-1,sum+i,f[h,sum,rem].b]);
其中,merge函數表示合併兩個記錄,它的僞代碼是:
function merge(x,y);
x.a:=x.a+y.a;
x.b:=y.b;
return x;
我們首先求出L和R的最近公共祖先,然後從 L所在的葉子向上走,每走一步就計算所有右側的兄弟子樹 。走到 LCA的下一層後,再向右走到R的祖先。 然後向下走到R所在葉子, 計算途徑的左側兄弟子樹。
code
2.
3.
4.
5.