题目链接:
题意:
给定一个 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;
}