目錄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
表示這一位有沒有最高位限制。
...
是表示有的題目可能需要一些別的。
先撇下zero
和limit
不管,我們繼續設計 dfs 函式。
那麼這個zero
和limit
呢?
zero
是前導零,limit
表示限制。
因為前導零是不計入數字限制,所以特別要注意前導零不能對數字統計造成干擾,於是我們需要注意前導零的限制,在記憶化搜尋的時候對於有前導零限制的數我們不能記憶化。
limit
表示限制,這個限制是幹什麼用的呢?
考慮以下資料:
0-47382
,現在已經填了473??
那麼第四位我們要怎麼列舉呢?
如果我們直接列舉0-9
,那麼在列舉到9
的時候就會發現:4739?
這個數字不在範圍內。於是我們需要引入limit
做出限制。
怎麼處理?
首先看zero
的處理。
解釋:上一位zero
為真表示前面所有位都是 0,而這一位也是 0,於是乎數字為00..0??...?
,那麼zero
接下來為真,告訴後面的數字這一位的 0 是前導零。
再看limit
的處理。
解釋:上一位limit
為真表示前面都是最高位,這一位也是最高位,於是乎從最前面到這一位都是最高位,那麼limit
接下來為真,告訴後面的數字這一位是最高位。
需要特別注意的是,當zero=0
或limit=0
的時候不能記憶化!(當然你可以加兩個狀態表示zero
和limit
,這樣可以)
在搜尋時我們按照一般的搜尋套路,特別注意對zero
和limit
的處理。
**如下:
#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
。
這裡再給乙個板子,這個板子是將zero
和limit
也加入記憶化的狀態裡面的。
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...