數字dp入門

2022-05-29 20:45:08 字數 3902 閱讀 6233

首先我們要清楚數字dp解決的是什麼問題:

求出在給定區間 [a,b] 內,符合條件 f(i) 的數 i 的個數。條件 f(i) 一般與數的大小無關,而與數的組成有關

由於數是按位dp,數的大小對複雜度的影響很小

這裡我們使用記憶化搜尋實現數字dp。本質上記搜其實就是dp,下文會重點介紹dp值的使用和記錄

從起點向下搜尋,到最底層得到方案數,一層一層向上返回答案並累加,最後從搜尋起點得到最終答案。

對於 [l,r] 區間問題,我們一般把他轉化為兩次數字dp,即找 [0,r] 和 [0,l-1] 兩段,再將結果相減就得到了我們需要的 [l,r]

如果理解了上述過程,我們需要考慮的就是怎樣判斷現在在哪一層,怎樣判斷當前的狀態——這就需要我們傳進一些參量。

dfs函式需要哪些參量?

首先是數字dp基本的量數字位數 pos ,記錄答案的 st ,最高位限制 limit (這個後面會講)

我們還需要乙個判斷判斷前導0的標記 lead (這個後面也會講)

由於數字dp解決的大多是數字組成問題,所以經常要比較當前位和前一位或前幾位的關係(根據題意而定),所以一般在dfs()中也要記錄前一位或前幾位數 pre 方便比較。

除此之外還可以傳進更多參量以區分狀態,視題意而定。

數字dp的狀態能記錄的最好都記錄上 ——lwz dalao

由於我們要搜的數可能很長,所以我們的直接最高位搜起

舉個例子:假如我們要從 [0,1000] 找任意相鄰兩數相等的數

顯然 111,222,888 等等是符合題意的數

但是我們發現右端點 1000 是四位數

因此我們搜尋的起點是 0000 ,而三位數的記錄都是 0111,0222,0888 等等

而這種情況下如果我們直接找相鄰位相等則 0000 符合題意而 0111,0222,0888 都不符合題意了

所以我們要加乙個前導0標記

如果當前位 lead=1 而且當前位也是0,那麼當前位也是前導0, pos+1 繼續搜;

如果當前位 lead=1 但當前位不是0,則本位作為當前數的最高位, pos+1 繼續搜;(注意這次根據題意st或其他引數可能發生變化)

當然前導 0 有時候是不需要判斷的,上述的例子是乙個有關數字結構上的性質,0會影響數字的結構,所以必須判斷前導0;而如果我們研究的是數字的組成(例如這個數字有多少個 1 之類的問題),0並不影響我們的判斷,這樣就不需要前導0標記了。總之,這個因題而異,並不是必須要標記(當然記了肯定是不會出錯的)

我們知道在搜尋的數字搜尋範圍可能發生變化;

當最高位是 1 ~ 4 時,第二位取值為 [0,9] ;

當最高位是 5 時,第二位取值為 [0,5] (再往上取就超出右端點範圍了)

為了分清這兩種情況,我們引入了limit標記:

若當前位 limit=1 而且已經取到了能取到的最高位時,下一位 limit=1 ;

若當前位 limit=1 但是沒有取到能取到的最高位時,下一位 limit=0 ;

若當前位 limit=0 時,下一位 limit=0 。

我們設這一位的標記為 limit ,這一位能取到的最大值為 res ,則下一位的標記就是 i==res && limit ( i 列舉這一位填的數)

最後我們考慮dp陣列下標記錄的值

本文介紹數字dp是在記憶化搜尋的框架下進行的,每當找到一種情況我們就可以這種情況記錄下來,等到搜到後面遇到相同的情況時直接使用當前記錄的值。

dp陣列的下標表示的是一種狀態,只要當前的狀態和之前搜過的某個狀態完全一樣,我們就可以直接返回原來已經記錄下來的dp值。

再舉個例子

假如我們找 [0,123456] 中符合某些條件的數

假如當我們搜到 1000?? 時,dfs從下返上來的數值就是當前位是第 5 位,前一位是 0 時的方案種數,搜完這位會向上反,這是我們可以記錄一下:當前位第 5 位,前一位是 0 時,有這麼多種方案種數

當我們繼續搜到 1010?? 時,我們發現當前狀態又是搜到了第 5 位,並且上一位也是 0 ,這與我們之前記錄的情況相同,這樣我們就可以不繼續向下搜,直接把上次的dp值返回就行了。

注意,我們返回的dp值必須和當前處於完全一樣的狀態,這就是為什麼dp陣列下標要記錄 pos,pre 等參量了。

最重要的來了————————————————————

接著上面的例子,範圍 [0,123456]

如果我們搜到了 1234?? ,我們能不能直接返回之前記錄的:當前第 5 位,前一位是 4 時的dp值?

答案是否定的

我們發現,這個狀態的dp值被記錄時,當前位也就是第 5 位的取值是 [0,9] ,而這次當前位的取值是 [0,5] ,方案數一定比之前記錄的dp值要小。

當前位的取值範圍為什麼會和原來不一樣呢?

如果你聯想到了之前所講的知識,你會發現:現在的 limit=1 ,最高位有取值的限制。

因此我們可以得到乙個結論:當 limit=1 時,不能記錄和取用dp值!

類似上述的分析過程,我們也可以得出:當 lead=1 時,也不能記錄和取用dp值!

p.s.當然沒有這麼絕對的說……因題而異的說……

以上就是計畫搜尋的完整步驟。

附圖:

模板:

1 ll dfs(int pos,int pre,int st,……,int lead,int limit)//記搜2

15if(!limit&&!lead) dp[pos][pre][st]……[……]=ret;//

當前狀態方案數記錄

16return

ret;17}

18 ll part(ll x)//

把數按位拆分

1925

intmain()

2634

return0;

35 }

例題:【題意簡述】

定義乙個正整數的價值是把這個數的十進位制寫出來之後,最長的等差子串的長度。

【分析】

這道題很顯然是一道數字dp,那麼我們應該怎麼樣設計狀態和轉移呢?

數字位置,前一位數,等差數列共差是一定要記錄的

我們還要把當前最大價值和整個數最大值也作為狀態

dp過程見**注釋(數字dp主要還是套板子呀)

#includeusing

namespace

std;

typedef

long

long

ll;int t,n,m,len,a[20

];ll l,r,dp[

20][15][25][25][20

];ll dfs(

int pos,int pre,ll st,ll sum,int d,int lead,int

limit)

//pos搜到的位置

//pre前一位數

//st當前公差最大差值

//sum整個數字的最大價值

//d共差

//lead判斷是否有前導0

//limit判斷是否有最高位限制

//沒有前導0和最高限制時可以直接記錄當前dp值以便下次搜到同樣的情況可以直接使用。

return (!limit&&!lead)?dp[pos][pre][st][sum][d+10]=ret:ret;

}ll part(ll x)

intmain()

return0;

}

數字DP入門 數字DP模板

數字dp是一種計數用的dp,一般就是要統計乙個區間 le,ri 內滿足一些條件數的個數。所謂數字dp,字面意思就是在數字上進行dp咯。數字還算是比較好聽的名字,數字的含義 乙個數有個位 十位 百位 千位.數的每一位就是數字啦!之所以要引入數字的概念完全就是為了dp。數字dp的實質就是換一種暴力列舉的...

數字DP入門

數字dp顧名思義就是對數字運用dp思想,將每位數看成下一位的子狀態,即個位看成十位的子狀態,十位看成百位的子狀態。下面給出狀態方程 dp i j dp i 1 k k 0,1,2,3.9 dp i j 表示i位數,首位是j的數字有多少符合要求的,我們從低位逐步遞推求到高位。下面拿一道入門題來具體說明...

數字dp入門

數字dp,顧名思義 對數的位數進行操作。一般 題目會與數的位數相關。數字dp最重要的是其dp陣列的確定,根據需要 可以確定多維dp陣列,一般dp陣列的每一對下標 都只能唯一確定乙個狀態,或者 對結果並不影響。dp陣列一定會有一維表示 當前列舉到的 位數。模版 如 此處設立的dp陣列為二維 int d...