1. 程式人生 > >【專題】數位DP

【專題】數位DP

aid dfs .com 合並 all 習慣 else net 普通

【資料】

★記憶化搜索:數位dp總結 之 從入門到模板 by wust_wenhao

論文:淺談數位類統計問題 數位計數問題解法研究

【記憶化搜索】

數位:數字從低位到高位依次為0~len-1

高位限制limit=limit&&i==a[pos]

前導零lead=lead&&i==0

數位pos=pos-1(第0位是個位,第-1位直接返回)

前綴狀態state(表示(pos,len]的狀態)

f[pos][state]表示前綴狀態為state,數位[0,pos]不受限的答案。

最後,詢問差分。

技術分享
typedef long long ll;  
int a[20];  
ll dp[
20][state];//不同題目狀態不同 ll dfs(int pos,/*state變量*/,bool lead/*前導零*/,bool limit/*數位上界變量*/)//不是每個題都要判斷前導零 { //遞歸邊界,既然是按位枚舉,最低位是0,那麽pos==-1說明這個數我枚舉完了 if(pos==-1) return 1;/*這裏一般返回1,表示你枚舉的這個數是合法的,那麽這裏就需要你在枚舉時必須每一位都要滿足題目條件,也就是說當前枚舉到pos位,一定要保證前面已經枚舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */ //第二個就是記憶化(在此前可能不同題目還能有一些剪枝)
if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state]; /*常規寫法都是在沒有限制的條件記憶化,這裏與下面記錄狀態是對應,具體為什麽是有條件的記憶化後面會講*/ int up=limit?a[pos]:9;//根據limit判斷枚舉的上界up;這個的例子前面用213講過了 ll ans=0; //開始計數 for(int i=0;i<=up;i++)//枚舉,然後把不同情況的個數加到ans就可以了 {
if() ... else if()... ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變量傳參都是這樣寫的 /*這裏還算比較靈活,不過做幾個題就覺得這裏也是套路了 大概就是說,我當前數位枚舉的數是i,然後根據題目的約束條件分類討論 去計算不同情況下的個數,還有要根據state變量來保證i的合法性,比如題目 要求數位上不能有62連續出現,那麽就是state就是要保存前一位pre,然後分類, 前一位如果是6那麽這意味就不能是2,這裏一定要保存枚舉的這個數是合法*/ } //計算完,記錄狀態 if(!limit && !lead) dp[pos][state]=ans; /*這裏對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裏就是lead就完全不用考慮了*/ return ans; } ll solve(ll x) { int pos=0; while(x)//把數位都分解出來 { a[pos++]=x%10;//個人老是喜歡編號為[0,pos),看不慣的就按自己習慣來,反正註意數位邊界就行 x/=10; } return dfs(pos-1/*從最高位開始枚舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛 } int main() { ll le,ri; while(~scanf("%lld%lld",&le,&ri)) { //初始化dp數組為-1,這裏還有更加優美的優化,後面講 printf("%lld\n",solve(ri)-solve(le-1)); } } 謝謝dalao的模板QAQ
View Code

這個強大的模板引用自數位dp總結 之 從入門到模板 by wust_wenhao,萬分感謝> <!

例題:【HDU】6148 Valley Numer

【技巧】

1.DP初始化只在多組數據外,過程中f數組可以重復使用。

2.減法的藝術:將記憶化【前綴和為sum時滿足條件的個數】改為記憶化【前綴和為sum,對後面還有all-sum需求時滿足條件的個數】,有可能優化空間。

3.計數轉求和:求解滿足條件的數字的和,對於每一位i算對答案的貢獻,即sum+=i*10^pos*cnt,其中cnt為數字個數,然後同時記憶化數字個數num和總和sum即可。

如果是平方和,關鍵就在對於pos位的數字i,f=i*10^pos,x表示pos位後面的整個數字,將當前平方和分離為(f+x)^2,則有f^2+2*f*x+x^2,最大的問題在於x要和f相乘。

但是,f相同!

所以對於x求sigma後,對於同一個f,公式就可以全部合並變成cnt*f^2+2*f*sigma(x)+sigma(x^2),顯然就要維護cnt,sum,sqsum,其中sqsum也正是當前正在求的平方和,sum是普通的和,cnt是數字個數。

三個東西可以並成結構體傳遞,也可以用傳地址。

4.另一種問題:求滿足條件的第n個數字,預處理f[i]表示長度為i的數字(不含前導0)的個數。

這樣就可以確定第n個數字的長度。

然後從最高位開始按位確定。

5.論文筆記:

技術分享
(一)

註意f[i]表達的觀點是高度,基於同一高度答案相同的現象。

f[i][j]表示高度i,恰好含有j個1的數的個數,不考慮該高度自身的0和1。

葉子節點所在高度為0,依次往上疊加。

calc過程:從上到下枚舉高度<1><2>(max~1,不能到0,考慮到倒數第二層為止)

<1>若x在本高度為1,tot++<2>若x在下個高度為1,則ans加下個高度的0的答案。(實際已經將0~max所有層都考慮完畢)

<3>考慮x本身可否ans++

k進制:將n化為k進制存進數組,左起第一個非01數字改為1,後面全部改為1,得到數字看成二進制就是不大於n的最大可以用01表示的數字了。

從數位角度考慮,最低位為0位,f[i][j]表示0~i-1位數字任意變換恰好含有j個1的數的個數。

calc(n,k)求0~n中恰好k個1的數字個數,通過將n中的每個1變為0,就可以每次加上0~i-1位數字任意變換恰好含有k-tot個1的數的個數(f[i-1][k-tot])。

真正計算結果時,f[i][j]是很少拿上來的,f[i][j]一般只用於calc中。

(二)【SPOJ】1182 Sorted bit sequence

註意:位數只能從31位開始到1位!

(三)【SPOJ】2319 BIGSEQ - Sequence
View Code

【遞推】【BZOJ】1833 [ZJOI2010]count 數字計數

【專題】數位DP