數位DP——P2602 [ZJOI2010]數字計數
阿新 • • 發佈:2022-03-24
題目描述
給定兩個正整數 \(a\) 和 \(b\),求在 \([a,b]\) 中的所有整數中,每個數碼(digit)各出現了多少次。
輸入格式
僅包含一行兩個整數 \(a,b\),含義如上所述。
輸出格式
包含一行十個整數,分別表示 \(0\sim 9\)在 \([a,b]\) 中出現了多少次。
輸入輸出樣例
輸入
1 99
輸出
9 20 20 20 20 20 20 20 20 20
說明/提示
資料規模與約定
對於 \(30\%\) 的資料,保證 \(a\le b\le10^6\) ;
對於 \(100\%\) 的資料,保證 \(1\le a\le b\le 10^{12}\) 。
析(引用oiwiki)
發現對於滿 \(i\)
有了 \(dp\) 陣列,我們來考慮如何統計答案。將上界按位分開,從高到低列舉,不貼著上界時,後面可以隨便取值。貼著上界時,後面就只能取 0 到上界,分兩部分分別計算貢獻。最後考慮下前導零,第 i 位為前導 0 時,此時 1 到 \(i - 1\) 位也都是 0,也就是多算了將 \(i - 1\)
做了這道題,我們可以發現,數位dp的時間複雜度和數字大小是沒有太大關係的,數位dp的速度極快,快到幾乎為常數,空間複雜度也不用開很高的陣列。
#include <bits/stdc++.h> #define ll long long using namespace std; ll l, r; ll dp[14]; ll rs[14]; ll ans1[14], ans2[14]; ll sum[14]; ll a[14]; void init() { // dp[i] 表示滿i位的數字個數,只需要一維就夠了,因為滿i位的時候,每個數字的個數是一樣的 rs[0] = 1; for (int i = 1; i <= 13; i++) { dp[i] = dp[i - 1] * 10 + rs[i - 1]; rs[i] = 10 * rs[i - 1]; } // for (int i = 1; i <= 13; i++) // { // cout << dp[i] << endl; // } } void work(ll n, ll * ans) { ll tmp = n; int len = 0; while (n != 0) // 用a儲存n的每一位 { len ++; a[len] = n % 10; n /= 10; } for (int i = len; i >= 1; i--) { for (int j = 0; j < 10; j++) ans[j] += dp[i - 1] * a[i]; for (int j = 0; j < a[i]; j++) ans[j] += rs[i - 1]; tmp -= rs[i - 1] * a[i]; ans[a[i]] += tmp + 1; ans[0] -= rs[i - 1]; // 前導0需要特殊處理一小下 } } int main() { ios::sync_with_stdio(false); // 輸入加速(用處不大) cin >> l >> r; init(); work(r, ans1); work(l - 1, ans2); // 這裡算出0~r的結果和0~l-1的結果,最後相減就是l~r的結果 for (int i = 0; i < 10; i++) cout << ans1[i] - ans2[i] << " "; cout << endl; return 0; }