The 2018 ACM-ICPC上海大都会赛 J Beautiful Numbers(数位dp)

题目链接:

Beautiful Numbers

 

题意:

给定一个 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 
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;
}

 

你可能感兴趣的:(ACM-数位DP)