7月6日(淼)——數位dp
阿新 • • 發佈:2021-07-06
前言
但拎出來寫部落格的原因如標題所示。實際上就是想水部落格。以後這個“(淼)”系列會有心情去更的。
所以以後這個系列的標題名暫時定為《x月x日(淼)——(此處填水的內容)》
數位dp我基本都寫記搜。目的是學會怎麼使用那個模板。
題外話:求助!HDU怎麼進去啊,我想測題目!
題目選做
本來想叫題目選講的,但是想了想,這些題目基本都是看了別人的題解,模仿寫出來的,這篇部落格的目的也是加深自己的理解。所以還是叫“題目選做”比較好。
我一直很贊同奧術師們(出自《奧術神座》)把自己的一些想法和知識寫成論文這種形式,這樣某種程度來說,確實能很有收穫,雖說沒有所謂的現實反饋那麼誇張……
那麼話歸正題:
正文
HDU 不要62
題目描述:
給定一個區間\([L,R]\) ,求這個區間中有多少個不含\(4\)和\(62\)的數
資料範圍:
\(0≤L≤R<10^6\)
題目分析:
寫動態規劃最關鍵的是設計狀態和找轉移(我覺得),所以先考慮怎麼設計狀態。
數位dp往往用字首和的方法把對\([L,R]\)的詢問改為對\([0,R]-[0,L-1]\)的詢問。所以設\(dp\)陣列的第一維是查到了數的第幾位。(暫定從高向低查)
題目中的不含\(4\)可以用簡單的在搜下一位時,就判斷此為是否是\(4\),如果是,就\(continue\)這種方法來剪掉。
所以我們\(dp\)陣列的第二位就記,這一位是否是\(6\)
關於\(limit\):
\(limit\)是最高位限制,來表示下一位應該取進位制下的最大值還取原數限制下的最大值。
設\(limit=1\)表示這一位現在取了原數限制下的最大值。
比方說原數是:\(123456\)
現在填到了:\(123xxx\)
此時下一位受原數限制,只能最大填到\(4\)
如果現在填到了:\(1233xx\)
那麼下一位受進位制的限制,最大填到\(9\)
(此處的‘x’指還未填的數)
套用板子有以下程式碼
#include<bits/stdc++.h> using namespace std; const int N=63;//正常來說,一個數的位數不會超過63 int a[N],dp[N][2],l,r; int dfs(int len,bool pre,bool limit){//分別代表位數、是否是6(是為1)、最高位限制 if(len==0) return 1; if(!limit && dp[len][pre]!=-1) return dp[len][pre]; //這裡,如果這個數搜過了,且它的值可以被更新,它在這步return了 int qwq=0,res=limit?a[len]:9;//所以進入這步的都是沒算過或不能記錄的 for(int i=0;i<=res;i++){ if(i==4) continue;//如題,如果是4是不可能的 if(pre==1&&(i==2)) continue;//如題,如果兩位是62,也不行 bool n_limit= limit && (i==res); //如果limit=1,說明上一位到了原數限制的最高位,此時res=這一位原數限制的最高位 //那麼對於下一位,如果這一位的i=res,下一位的limit就等於1 bool n_pre=(i==6)?1:0; qwq+=dfs(len-1,n_pre,n_limit); } return limit?qwq:dp[len][pre]=qwq;//limit=1時,dp是不能被更新的 } int work(int x){ int len=0; memset(dp,-1,sizeof(dp)); while(x)a[++len]=x%10,x/=10; return dfs(len,0,1); } int main(){ cin>>l>>r; cout<<work(r)-work(l-1); return 0; }
之前看了題解寫了一遍,現在終於自己也能寫出來了……
座右銘:我從來沒有見過這樣陰鬱而又光明的日子。