(P1836) 数页码(数位DP)

这篇博客介绍了如何使用动态规划解决数位计数的问题,具体到数页码的情况。通过分析数字计数的思路,简化处理,不考虑前导0,给出两种不同的代码实现方式,分别计算每个数位的贡献。第一种方式记录出现次数,第二种方式记录出现次数乘以权值。这两种方法都利用了动态规划的思想,避免重复计算。
摘要由CSDN通过智能技术生成

题目链接:数页码 - 洛谷

分析:这道题目与数字计数那道题基本上是一样的,那道题目里面是求出0~9每一位数字出现的次数,而这道题目是求出每个数每位上的数的和,这就可以先求出来每一位出现的次数,再乘以该位对应的权重即可,这道题不需要考虑0了,所以完全就不需要考虑前导0,所以会简单一些,建议大家看一下我写的关于数字计数的那篇博客,里面有一些需要注意的点,详细介绍了每一位出现次数的求解方法,而且介绍了一些常见错误,或许对大家还是有所帮助的。下面附上数字计数博客地址:P2602 [ZJOI2010]数字计数(数位DP)_AC__dream的博客-CSDN博客

鉴于在数字计数那道题目中已经详细介绍了每一位出现次数的求法,本道题目我就直接放上代码;

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=15;
typedef long long ll;
ll f[N][N],a[N];
ll dfs(int pos,int cnt,int limit,int t)
{
	if(!pos) return cnt;
	if(!limit&&f[pos][cnt]!=-1) return f[pos][cnt];
	ll ans=0;
	int up=limit?a[pos]:9;
	for(int i=0;i<=up;i++)
		ans+=dfs(pos-1,cnt+(i==t),limit&&(i==up),t);
	if(!limit) f[pos][cnt]=ans;
	return ans;
}
ll solve(ll x,int t)
{
	if(!x) return 0;
	memset(f,-1,sizeof f);
	int pos=0;
	while(x)
	{
		a[++pos]=x%10;
		x/=10;
	}
	return dfs(pos,0,1,t);
}
int main()
{
	ll n,ans=0;
	cin>>n;
	for(int i=1;i<=9;i++)
		ans+=i*solve(n,i);
	printf("%lld",ans);
	return 0;
}

当然这道题目也可以不用一位一位求,我们可以直接令f[pos][cnt]表示后pos位任选且除了后pos位以外的位上各位数字之和为cnt的贡献,这样我们就可以一次搜索求出最终答案了,但是此时的cnt记录的不再是出现次数,而是出现次数乘以其权值

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=15;
typedef long long ll;
ll f[N][N*N],a[N];
ll dfs(int pos,int cnt,int limit)
{
	if(!pos) return cnt;
	if(!limit&&f[pos][cnt]!=-1) return f[pos][cnt];
	ll ans=0;
	int up=limit?a[pos]:9;
	for(int i=0;i<=up;i++)
		ans+=dfs(pos-1,cnt+i,limit&&(i==up));
	if(!limit) f[pos][cnt]=ans;
	return ans;
}
ll solve(ll x)
{
	if(!x) return 0;
	memset(f,-1,sizeof f);
	int pos=0;
	while(x)
	{
		a[++pos]=x%10;
		x/=10;
	}
	return dfs(pos,0,1);
}
int main()
{
	ll n,ans=0;
	cin>>n;
	printf("%lld",solve(n));
	return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值