「學習筆記」數位 DP
意義不大的題不寫了,
點擊查看目錄
目錄
- 「學習筆記」數位 DP
- 概述
- 例題
- P2657 [SCOI2009] windy 數
- 思路
- 代碼
- P4317 花神的數論題
- 思路
- P4124 [CQOI2016]手機號碼
- 思路
- 代碼
- haha數
- 題意
- 思路
- 代碼
- 0和1的熟練
- 題意
- 思路
- 代碼
- 蒼與紅的試煉
- 題意
- 思路
- 代碼
概述
數位 DP 一般用來解決「在一個較大的區間內統計具有一定特征的數的數量」的問題,
數位 DP 一般有兩種做法:
- 遞推法:首先需要預處理出具有一定條件的數的個數,然后將上限按數位拆分開來考慮貢獻,
- 暴搜法:直接記憶化搜索具有特定條件的數的個數,
例題
P2657 [SCOI2009] windy 數
思路
本題使用遞推,
設 \(f_{i,j}\) 表示最高位為 \(j\) 的 \(i\) 位 windy 數數量(\(j\) 可以為 \(0\)),顯然:
\[f_{i, j} = \begin{cases} 1 &i=1\\ \sum_{k=0}^{9}[\left|j - k\right| \ge 2]f_{i - 1, k} &\text{otherwise} \end{cases} \]然后計算答案,把上限 \(x\) 的每一位拆開(假設 \(x\) 有 \(l\) 位),考慮到第 \(i\) 位時,前 \(l-i\) 位與 \(x\) 相同,后 \(i\) 位的方案數用 \(f_{i,j}\) 計算,
但是有一個問題:\(0001\) 去除前導零后合法,但是 \(10001\) 不合法,因此為了保證之后計算的數依然合法,\(f_{4, 0}\) 不會記錄像 \(0001\) 這樣去除前導零后合法但在最高位再加上一個數后不合法的數的數量,
此時我們需要一個輔助陣列 \(g_{i}\),即被漏算了的首位為 \(0\) 的 \(i\) 位數的數量,當第 \(i-1\) 位大于等于 \(2\) 時,首位為 \(0\) 的 \(i\) 位數不會被漏算(因為此時第 \(i\) 位和第 \(i-1\) 差值大于等于為 \(2\),仍然合法),但是第 \(i-1\) 位小于 \(2\) 的數會被漏算,所以易得:
\[g_{i} = g_{i - 1} + f_{i - 1, 0} + f_{i - 1, 1} \]計算答案時加上即可,
代碼
點擊查看代碼
const ll N = 15, inf = 1ll << 40, P = 1e9 + 7;
namespace SOLVE {
ll a, b, f[N][N], g[N];
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
inline ll GetAns (ll x) {
ll len = 0, num[N] = {0}, sum = 0;
while (x) num[++len] = x % 10, x /= 10;
for_ (i, len, 1) {
if (i == len) {
_for (j, 0, num[i] - 1) sum += f[i][j];
sum += g[i];
continue;
}
_for (j, 0, num[i] - 1) {
if (abs (j - num[i + 1]) < 2) continue;
sum += f[i][j];
}
if (abs (num[i] - num[i + 1]) < 2) break;
}
return sum;
}
inline void In () {
a = rnt (), b = rnt ();
return;
}
inline void Solve () {
_for (i, 0, 9) f[1][i] = 1;
g[1] = 2;
_for (i, 2, 10) {
g[i] = g[i - 1] + f[i - 1][0] + f[i - 1][1];
_for (j, 0, 9) {
_for (k, 0, 9) {
if (abs (j - k) < 2) continue;
f[i][j] += f[i - 1][k];
}
}
}
return;
}
inline void Out () {
printf ("%lld\n", GetAns (b + 1) - GetAns (a));
return;
}
}
P4317 花神的數論題
思路
使用遞推,
\(f_{i, j, k}\) 表示有 \(i\) 位,最高位為 \(j(j\in\{0,1\})\),有 \(k\) 個 \(1\) 的二進制數,顯然:
\[\begin{aligned} f_{i, 0, j} &= \begin{cases} 1 &j=0\\ f_{i - 1, 0, j} + f_{i - 1, 1, j} &\text{otherwise} \end{cases}\\ f_{i, 1, j} &= \begin{cases} 1 &i=1\\ f_{i - 1, 0, j - 1} + f_{i - 1, 1, j - 1} &\text{otherwise} \end{cases} \end{aligned} \]然后由于要算乘積,要使用快速冪計算有 \(k\) 個 \(1\) 的二進制數的乘積總和,
P4124 [CQOI2016]手機號碼
思路
使用暴搜,
定義函式:
ll Dfs (ll p, ll a, ll b, ll sm, ll fr, ll et, ll li)
其中,\(p\) 表示第幾位,\(a\) 表示第 \(p\) 位的數,\(b\) 表示第 \(p+1\) 位的數,\(sm\) 表示是否已經存在一樣的連續三個數,\(fr\) 是否出現過 \(4\),\(et\) 是否出現過 \(9\),\(li\) 前面幾位是否和上限完全一樣(這個會影響搜索時下一位的的數,如果完全一樣則下一位的的數不能超過上限的這一位,否則可以到 \(9\)),
代碼
點擊查看代碼
ll Dfs (ll p, ll a, ll b, ll sm, ll fr, ll et, ll li) {
if (fr && et) return 0;
if (!p) return sm;
if (!li && f[p][a][b][sm][fr][et] > 0) return f[p][a][b][sm][fr][et];
ll sum = 0, mx = li ? t[p] : 9;
_for (i, 0, mx) sum += Dfs (p - 1, i, a, sm || (i == a && i == b), fr || (i == 4), et || (i == 8), li && i == mx);
if (!li) f[p][a][b][sm][fr][et] = sum;
return sum;
}
inline ll GetAns (ll x) {
if (x < 1e10) return 0;
ll len = 0, sum = 0;
while (x) t[++len] = x % 10, x /= 10;
memset (f, -1, sizeof (f));
_for (i, 1, t[len]) sum += Dfs (len - 1, i, 0, 0, i == 4, i == 8, i == t[len]);
return sum;
}
haha數
題意
一個正整數,當且僅當在十進制表示下,它的各位非零數字能整除該數本身時(即它的所有非零位的數字都是該數的約數),被稱作 haha 數,給定 \(l\) 和 \(r\),求在 \([l, r]\) 范圍內 haha 數的個數,
\(1\le l \le r \le9\times10^{18}\),
思路
使用暴搜,
定義函式:
ll Dfs (ll p, ll md, ll sta, ll li)
其中,\(p\) 表示第幾位,\(md\) 表示當前的數膜 \(\operatorname{lcm}(1,2,3,4,5,6,7,8,9)=2520\) 的余數,\(sta\) 表示 \(2\sim8\) 的數是否出現過的狀態,\(li\) 前面幾位是否和上限完全一樣,
最后判斷合法時,看 \(md\) 是否是 \(sta\) 記錄的所有出現過的數的倍數即可,
代碼
點擊查看代碼
inline ll w (ll x) { return (x < 2) ? 0 : (1 << (x - 2)); }
inline bool Check (ll md, ll sta) {
_for (i, 2, 9) if ((sta & w (i)) && (md % i)) return 0;
return 1;
}
ll Dfs (ll p, ll md, ll sta, ll li) {
if (!p) return Check (md, sta);
if (!li && f[p][md][sta] >= 0) return f[p][md][sta];
ll sum = 0, mx = li ? t[p] : 9;
_for (i, 0, mx) sum += Dfs (p - 1, (md * 10 + i) % 2520, sta | w (i), li & (i == mx));
if (!li) f[p][md][sta] = sum;
return sum;
}
0和1的熟練
題意
有一個程式員,他使用只有兩個按鍵的鍵盤打字,這兩個按鍵就是 \(0\) 和 \(1\),只有達到高端境界的人才能出入此境,記住,只有從 \(l\) 到 \(r\) 的所有數都手打過一遍了才能練就如此功夫,作為真正的高手,你一定能快速地回答區間 \([l,r]\) 中有多少數字的二進制表示(不含前導零)中 \(0\) 的個數不少于 \(1\) 的個數,因為你每天都進行一遍這個練習,
\(1\le l<r\le2\times10^9\),
思路
使用暴搜,
定義函式:
ll Dfs (ll p, ll c1, ll len, ll li)
其中,\(p\) 表示第幾位,\(c1\) 表示當前的數有幾個 \(1\),\(len\) 當前的數變為二進制后有多少位,\(li\) 前面幾位是否和上限完全一樣,
代碼
點擊查看代碼
ll Dfs (ll p, ll c1, ll len, ll li) {
if (!p) return len ? (len - c1 >= c1) : 1;
if (!li && f[p][c1][len] >= 0) return f[p][c1][len];
ll sum = 0, mx = li ? t[p] : 1;
_for (i, 0, mx) sum += Dfs (p - 1, c1 + (i == 1), len + (len || i), li & (i == mx));
if (!li) f[p][c1][len] = sum;
return sum;
}
蒼與紅的試煉
題意
輝煌的時代終將落幕嗎,只有時間的車輪滾滾向前,
來自蒼空之上的,是點亮前路的希望,是劃破陰云的曙光,
為協助天城追回那被鉛色未來蒙蔽前路之人,你需要完成天城的試煉以取得天城的認可,
漫櫻飛舞的古城中,有一些標有數字 \(0\sim9\) 的御守,現在天城給了你一個正整數 \(d\),你需要按照某種順序排列一些御守,寫下由這些御守拼成的數字 \(x\),并保證 \(x\) 能被 \(d\) 整除,
然而想通過「重櫻之鬼謀」的考驗哪有這么簡單,天城還指定了一個正整數 \(s\),你必須保證你所選擇的所有御守上的數字之和等于 \(s\) 才能滿足要求,同時為了檢驗你的能力,天城要求你給出滿足要求的最小的 \(x\),當然如果不存在滿足要求的 \(x\),你也必須要向天城指出這一點,
跨越時間之長河,斬斷命運之枷鎖,挑戰者啊,窮盡武略與智謀,迎接來自頂點的試煉吧,
\(1\le d\le500, 1\le s\le 5000\)
思路
沒有上限,所以直接深搜會爆掉,
那么考慮廣搜,放入佇列一個數對 \((a, b)\),表示當前數膜 \(d\) 等于 \(a\),每一位數之和等于 \(b\),顯然當 \(a=0,b=s\) 時搜索結束,
狀態只有 \(500\times5000=2500000\) 種,記憶化一下即可,
代碼
點擊查看代碼
namespace SOLVE {
const ll N = 1e7;
ll d, s, f[N], g[N], jl[N], ans;
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
void Print (ll p) {
if (p == 1) return;
Print (g[p]);
printf ("%lld", f[p]);
}
inline void In () {
d = rnt (), s = rnt ();
return;
}
inline void Solve () {
std::queue <ll> q;
q.push (0);
jl[0] = 1;
ll h = 0, t = 1;
while (!q.empty ()) {
ll fr = q.front ();
++h, q.pop ();
_for (i, 0, 9) {
ll dd = (fr % 501 * 10 + i) % d;
ll ss = fr / 501 + i;
ll num = dd + ss * 501;
if (ss > s) continue;
if (jl[num]) continue;
f[++t] = i, g[t] = h;
if (!dd && ss == s) {
ans = t;
return;
}
jl[num] = 1;
q.push (num);
}
}
return;
}
inline void Out () {
if (ans) Print (ans), puts ("");
else puts ("-1");
return;
}
}
本文來自博客園,作者:Keven-He,轉載請注明原文鏈接:https://www.cnblogs.com/Keven-He/p/NumberDP.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/549579.html
標籤:其他
