題目鏈接:
題意:
給定一個 n ,求 1 - n 中 Beautiful Numbers 的個數。一個數爲 Beautiful Numbers 當且僅當它是它所有數位上數字和的倍數。
思路:
數位dp的關鍵在於定義dp數組以確保該數位上進行如此選擇對答案的貢獻是唯一的。
定義dp數組:dp[pos][sum][res] :sum表示各數位上數字的和,res表示數值%mod 。
其中 mod 爲最終的數字各數位上數字的和。(sum最終要等於mod才計算答案)10^12以內的數mod最大爲位數*9,暴力枚舉即可。
分析唯一性:設到pos位,2個不同的數,及pos位以前的值有不同,但其各數位上數字的和相同,均爲sum,且膜上mod的值(設爲p)也相同,所以從pos到第一位他們所需要的數位和是相同的(均爲mod-sum),且組成的數字%mod的值(設爲 t )也是相同的(均爲 t=mod-p),即這兩個不同的數到此位爲止後面的計算都是完全一樣的,可行!
memset優化:上述dp數組在遍歷mod的時候每次都需要初始化,因爲不同mod情況下,dp數組記錄的數值不同。這樣每次都要初始化15*120*120的數組,單次複雜度爲15*120*120*108 = 2e7,100組詢問,複雜度爲 2e9 ,爆炸。但我們可以發現不同組詢問的時候,只要mod值一樣,其實dp數組存的數值是一樣的,因此爲了避免重複memset,我們可以給dp數組增開一維 [mod] 。即dp數組爲 :dp[pos][sum][res][mod] 。這樣只需要在 T組 外面memset一次即可。(以空間換時間)
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 5e5 + 100;
ll n, mod;
int bit[15];
ll dp[15][120][120][120];
ll dfs(int pos, ll val, int sum, int limit)
{
if (pos == 0) {
return (sum == mod) && (val % mod == 0);
}
if (!limit&&dp[pos][sum][val % mod][mod] != -1) return dp[pos][sum][val % mod][mod];
int up = limit ? bit[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; i++) {
if (sum + i > mod) break;
ans += dfs(pos - 1, val * 10 + i, sum + i, limit&&i == bit[pos]);
}
if (!limit&&sum != 0) dp[pos][sum][val % mod][mod] = ans;
return ans;
}
ll solve(ll x)
{
int cnt = 0;
while (x) {
bit[++cnt] = x % 10;
x /= 10;
}
ll ans = 0;
for (int i = 1; i <= cnt * 9; i++) {
mod = i;
ans += dfs(cnt, 0, 0, true);
}
return ans;
}
int main()
{
memset(dp, -1, sizeof(dp));
int T;
scanf("%d", &T);
int Case = 1;
while (T--)
{
scanf("%lld", &n);
printf("Case %d: ", Case++);
printf("%lld\n", solve(n));
}
return 0;
}