[一次数位dp]数页码 洛谷P1836

题目描述

一本书的页码是从 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值