咱們定義一個數是單調數,當且僅當構成這個數每個數位都是單調不降或不增的。c++
例如 \(123\) 和 \(321\) 和 \(221\) 和 \(111\) 是單調的,而 \(312\) 不是單調的。git
給定 \(T\) 組 \(l, r\),每次詢問 \([l, r]\) 中有幾個單調的數。數組
\(l, r \le 10 ^ {18}, T \le 10^4\)spa
今天 hihoCoder 居然 \(AK\) 了,真舒服(雖然題目很水,還被罰時坑慘了)qwqdebug
顯然考慮一個數位 \(dp\) ,不難發現咱們只須要記下當前在哪一位,以及最後一位是什麼數字就好了。code
而後對於不降和不增作個兩遍就好了,但有不少細節後面細講。ci
寫的時候發現本身摸索了一套數位 \(dp\) 的套路?(逃get
套路:it
咱們經常是要求 \(\le n\) 的有多少個知足要求的數。class
這個限制有些噁心,咱們須要多一位來看是否被限制。
咱們通常按位考慮,令 \(dp[i][0 / 1]\) 爲到從高到低考慮到第 \(i\) 位,當前有/沒被 \(n\) 限制。
咱們考慮把 \(n\) 按位拆下來,變成一個數組
lim[i]
,而後取出 \(n\) 的位數Bit
。每次考慮後一位放什麼數字就好了。具體實現以下(用刷表法方便轉移):
dp[0][0] = 1; for (int i = 0; i < Bit; ++ i) for (int now = 0; now <= 1; ++ now) for (int dig = 0; dig <= 9; ++ dig) if (now || (dig <= lim[i + 1])) dp[i + 1][now || (dig < lim[i + 1])] += dp[i][now];而後最後的答案就是
ans = dp[Bit][0] + dp[Bit][1];不難發現這樣寫,每一個位數的數都會被考慮到。由於咱們枚舉的時候容許了前綴 \(0\) 的存在。
而且若是存在前綴 \(0\) 那麼後面的全部數都不會存在限制了,能夠隨便填。
可是注意這樣的話,所有爲 \(0\) 的也會考慮進去,咱們日常要考慮是否 \(-1\) 就好了。
對於這道題咱們能夠類比這種方法去作。
首先把答案差分表示 \(ans = ans_r - ans_{l - 1}\) 。
而後令 dp[i][j][0/1]
爲到第 \(i\) 位,最後一次填 \(j\) ,有/沒 被 \(n\) 限制住的狀況。
直接這樣寫的話,遞增是沒有問題的,遞減的時候就會存在問題了。
由於咱們把前導 \(0\) 考慮進去了,結果致使沒有正確算上這部分貢獻。
因此咱們還要多一維,也就是 dp[i][j][0/1][0/1]
前三個同上,最後一個意義是當前徹底不/是前綴 \(0\) 。
而後轉移的時候也是枚舉數字,而後按照兩種狀況考慮下填的這個數的限制就好了。
注意有一些數會被遞增遞減算兩次,也就是 \(111\) ,\(3333\) ,這些徹底相同的數。能夠直接暴力枚舉減去就好了。
複雜度是 \(O(T * 18 * 10)\) 的。
碰到數位 \(dp\) 直接上套路去討論。
而後就須要對於具體問題具體分析了,根據題目要求設出須要的狀態。
有時候能夠根據須要卡卡狀態數,由於一般不可能到滿。
必定要寫個暴力拍,這個東西其實很好調?
建議看看代碼,其實寫的很簡潔?(可讀性也是很魯棒的qwq)
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("1770.in", "r", stdin); freopen ("1770.out", "w", stdout); #endif } typedef long long ll; ll dp[19][10][2][2]; int lim[19]; inline int Get_Bit(ll x) { int tot = 0; for (; x; x /= 10) lim[++ tot] = x % 10; reverse(lim + 1, lim + tot + 1); return tot; } inline ll Calc(ll n) { if (!n) return 0; ll ans = 0; For (opt, 0, 1) { Set(dp, 0); dp[0][0][0][1] = 1; int Bit = Get_Bit(n); For (i, 0, Bit - 1) For (j, 0, 9) For(now, 0, 1) For (fir, 0, 1) { For (dig, opt ? 0 : j, opt ? (fir ? 9 : j) : 9) if (now || dig <= lim[i + 1]) dp[i + 1][dig][now || (dig < lim[i + 1])][fir && !dig] += dp[i][j][now][fir]; } For (j, 0, 9) For(now, 0, 1) For (fir, 0, 1) ans += dp[Bit][j][now][fir]; -- ans; } For (dig, 1, 9) { ll tmp = dig; while (tmp <= n) -- ans, tmp = tmp * 10 + dig; } return ans; } int main() { File(); for (int cases = read(); cases; -- cases) { ll l, r; cin >> l >> r; printf ("%lld\n", Calc(r) - Calc(l - 1)); } return 0; }