题目描述
一本书的页码是从 1∼n 编号的连续整数:1,2,3,⋯,n。请你求出全部页码中所有单个数字的和,例如第 123 页,它的和就是 1+2+3=6。
输入格式
一行一个整数 n。
输出格式
一行,代表所有单个数字的和。
输入输出样例
输入 #1
3456789
输出 #1
96342015
说明/提示
1≤n≤10^9
题意: 求1到n的所有整数各位数字之和。
分析: 这道题有两种做法,一种是多次数位dp,每次计算单个数字的贡献,另一种是一次数位dp得到最终答案。第一种做法比较简单,本质就是统计某个数字在1~n的出现次数,故这里只记录第二种做法。
可以让dp数组记录后pos位任取时产生的贡献和,这时dp数字中状态不能只有位数,假设在第pos位之前的数字和为sum,当第一次后pos位可以任选时会先跑一个暴力得到在前几位和为sum时的贡献,并将其记录为dp[pos],接下来若前几位和为sum+1,此时第pos位仍能任选,那么这里直接返回dp[pos]就会出错,dp[pos]是在前几位和为sum时记录的答案,实际上此时的dp[pos]与真正的结果相差了10^pos,因此需要再开一维状态记录前几位的和,在前几位和相同的前提下无论前面几位如何选取最终结果都是相同的,所以这样就是正确的了。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#define int long long
using namespace std;
int dp[15][105], a[15];
int dfs(int pos, int sum, bool flag)
{
if(!pos)
return sum;
if(flag && dp[pos][sum] != -1)
return dp[pos][sum];
int ans = 0;
int up = flag?9:a[pos];
for(int i = 0; i <= up; i++)
ans += dfs(pos-1, sum+i, flag||i<up);
if(flag)
dp[pos][sum] = ans;
return ans;
}
int solve(int x)
{
int pos = 0;
while(x)
{
a[++pos] = x%10;
x /= 10;
}
return dfs(pos, 0, 0);
}
signed main()
{
int n;
cin >> n;
memset(dp, -1, sizeof dp);
cout << solve(n);
return 0;
}