DP專題 學習筆記 數字 DP

2022-10-09 18:42:07 字數 3371 閱讀 1659

目錄3. 練習題

update 2021/2/23:最近作者發現數字 dp 的 \(f\) 陣列初始化有問題,導致**出現了根本性錯誤(原理不變),現在已經糾正,對各位讀者造成的困擾深表歉意。

數字 dp,是一種 dp (廢話),專門用於數字統計類問題。

這種問題首次接觸可能會有些難理解,但是練過幾道題之後就會掌握套路了。

[scoi2009] windy 數

這是數字 dp 的基礎題,我個人認為比別的題目更好入門。

數字 dp 的題目通常都是這樣表達的:

問:在 \([l,r]\) 範圍內,滿足條件 \(p(x)\) 的數 \(x\) 有幾個?

比如例題:

問:在 \([a,b]\) 範圍內,滿足條件 \(相鄰數之差大於等於2\) 的數 \(x\) 有幾個?

數字 dp 的問題一般都滿足區間可減性

比如說例題,無論 \(a,b\) 怎麼變,windy 數始終是 windy 數,非 windy 數始終是非 windy 數。

於是乎,我們就可以使用差分將詢問變為 \(f(b)-f(a-1)\)。

其中 \(f(x)\) 表示在 \([1,x]\) 內有幾個滿足條件的數。

**裡面需要特別對 \(a=0\) 特判!

既然是數字 dp,那麼肯定跟數字有關。

那麼首先我們要拆分數字。假設拆分在 \(a\) 數字裡面,\(a_i\) 表示從低到高第 \(i\) 位(這是為了與**匹配)。\(cnt\) 為位數。

數字 dp 有兩種寫法:記憶化搜尋與直接遞推。

記憶化搜尋便於理解,直接遞推**簡潔。

本文章採用記憶化搜尋講解。

首先設計 dfs 函式:(ll即為long long)

ll dfs(int pos, int las, ..., int zero, int limit)

pos表示當前搜到第幾位,las表示上一位數字是什麼(windy 數需要上一位數字),zero表示是否為前導 0,limit表示這一位有沒有最高位限制。

...是表示有的題目可能需要一些別的。

先撇下zerolimit不管,我們繼續設計 dfs 函式。

那麼這個zerolimit呢?

zero是前導零,limit表示限制。

因為前導零是不計入數字限制,所以特別要注意前導零不能對數字統計造成干擾,於是我們需要注意前導零的限制,在記憶化搜尋的時候對於有前導零限制的數我們不能記憶化。

limit表示限制,這個限制是幹什麼用的呢?

考慮以下資料:

0-47382,現在已經填了473??

那麼第四位我們要怎麼列舉呢?

如果我們直接列舉0-9,那麼在列舉到9的時候就會發現:4739?這個數字不在範圍內。於是我們需要引入limit做出限制。

怎麼處理?

首先看zero的處理。

解釋:上一位zero為真表示前面所有位都是 0,而這一位也是 0,於是乎數字為00..0??...?,那麼zero接下來為真,告訴後面的數字這一位的 0 是前導零。

再看limit的處理。

解釋:上一位limit為真表示前面都是最高位,這一位也是最高位,於是乎從最前面到這一位都是最高位,那麼limit接下來為真,告訴後面的數字這一位是最高位。

需要特別注意的是,zero=0limit=0的時候不能記憶化!(當然你可以加兩個狀態表示zerolimit,這樣可以)

在搜尋時我們按照一般的搜尋套路,特別注意對zerolimit的處理。

**如下:

#include using namespace std;

typedef long long ll;

int l, r, f[20][10], a[20], cnt;

int read()

while (ch >= '0' && ch <= '9')

return sum * fh;

}int abs(int x)

int dfs(int pos, int las, int zero, int limit)

if (!zero && !limit) f[pos][las] = ans;

return ans;

}int get(int k)

return dfs(cnt, -2, 1, 1);

}int main()

#include using namespace std;

//sth.

int read()

while (ch >= '0' && ch <= '9')

return sum * fh;

}int dfs(int pos, ..., int zero, int limit)

if (!zero && !limit) f[...] = ans;

return ans;

}int get(int k)

return dfs(cnt, ..., 1, 1);

}int main()

這裡對**做乙個統一說明與解釋:

例題**的-2是為什麼?

處理方便,不需要特判。

注意板子裡面要特判一下l!=0

這裡再給乙個板子,這個板子是將zerolimit也加入記憶化的狀態裡面的。

int f[...][2][2];

int dfs(int pos, ..., int zero, int limit)

f[...][zero][limit] = ans;

return ans;

}

練習題傳送門:dp演算法總結&專題訓練1(概率/期望 dp & 數字 dp)

演算法筆記 數字dp

前言 當我們遇到某些題目的時候 比如像讓你統計l r這乙個區間內的數字和以及滿足條件的數有幾個這一類的題目 常常會因為區間太大而無法計算。這時候,我們就需要用上我們偉大的數字dp啦 數字dp的實質就是換一種暴力列舉的方式,使得新的列舉方式滿足dp的性質,然後記憶化就可以了。nm這本質上不還是記憶化搜...

數字DP專題

hdu 2089 不要62 hdu 3555不能出現連續的49 uestc 1307相鄰的數差大於等於2 hdu 3652 出現13,而且能被13整除。hdu 3709平衡數 light oj 1140兩個數之間的所有數中零的個數。lightoj 1032 二進位制數中連續兩個 1 出現次數的和 c...

演算法筆記 數字dp小結

入門ppt link1 記憶化方式 link2 link3 1 2 記憶化的數字dp 通常而言,有四個引數必須 dp pos,flag,limit pos表示當前正在列舉的數字。flag標誌已經列舉的字首是否某種性質 前面的數字和,是否含有某個數,前乙個列舉的數等等。當然flag可以有多個。limi...