hihoCoder #1770 : 單調數(數位dp)

題面

咱們定義一個數是單調數,當且僅當構成這個數每個數位都是單調不降或不增的。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;

}
相關文章
相關標籤/搜索