hdu3555 Bomb(数位dp+心得总结)

题目

给你一个数n,问[1,n]包括49的数有多少个,n<(1<<63)

思路来源

https://blog.csdn.net/u012860063/article/details/46820639

https://www.2cto.com/kf/201409/338701.html

题解

这题分前面出现49和后面出现49讨论……

都在代码的注释里了……

dp[i][0] i位数 不包含49的方案数
dp[i][1] i位数 不包含49且以9开头的方案数
dp[i][2] i位数 包含49的方案数

统计时维护是不是已经出现49和这一位填几

感觉数位dp统计答案的关键是,这一位填几QAQ

心得

统计完比这一位小的数开头的数的贡献之后,这一位就定下来了

模拟写数的过程,才有以下的三个经典操作,

枚举的i是数的位数,才会用a[0]来去记有多少位,

考虑在转移的过程中,需要什么状态才能继续往下转移,从而达到目的

 

数位dp还是很套路的,分三个步骤

①确定表达式,一般是dp[i][j]表示i位数最高位是j的方案数,但像本题就不一样

②预处理,考虑数字与数字之间的转移关系

③统计,从高到低枚举每一位a[i],统计[0,a[i]-1]对这一位的贡献,当前位一定能取[0,a[i]-1]

 

数位dp经典操作

①dp[0][0]=1,初值赋1

②a[a[0]+1]=0,高位赋0

③[0,x),solve(x+1),询问加1

代码1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
const int maxn=1e6+10;
typedef unsigned long long ull; 
using namespace std;
//dp[i][0] i位数 不包含49
//dp[i][1] i位数 以9开头 不包含49
//dp[i][2] i位数 包含49 
int t;
ull dp[20][3],a[20]; 
void init()
{
	dp[0][0]=1;
	for(int i=1;i<=18;++i)
	{
		for(int j=0;j<=9;++j)
		{
			dp[i][0]=dp[i-1][0]*10-dp[i-1][1];//dp[i-1][0]前填0-9 减去其中以9开头前填4的情况 
			dp[i][1]=dp[i-1][0];//dp[i-1][0]前填9即可
			dp[i][2]=dp[i-1][2]*10+dp[i-1][1];//dp[i-1][2]已经包含49前填0-9 加上以9开头前填4的情况 
		}
	} 
}
ull cal(ull x)
{
	ull ans=0;
	a[0]=0;
	while(x)
	{
		a[++a[0]]=x%10;
		x/=10;
    }
    a[a[0]+1]=0;//保证a[0]的上一位不为4 
    bool flag=0;//是否已出现49 
	for(int i=a[0];i>=1;--i)//从高到低统计
	{
		ans+=dp[i-1][2]*a[i];//这位可以填0-(a[i]-1)随便填 反正后面有49
		if(flag)ans+=dp[i-1][0]*a[i];//这一位随便填 反正前有49
		else if(a[i]>4)ans+=dp[i-1][1];//这一位可以填4 和后面的9开头的凑出49 
		if(a[i+1]==4&&a[i]==9)flag=1;//出现49了 
	} 
	return ans;
}
int main()
{
	init();
	scanf("%d",&t);
	while(t--)
	{
		ull x;
		scanf("%llu",&x);
		printf("%llu\n",cal(x+1));
	}
	return 0;
}

代码2(自己瞎搞了一个也过了)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
const int maxn=1e6+10;
typedef unsigned long long ull; 
using namespace std;
//dp[i][j] j为最高的i位数的答案
//f[i] 后面缀几个0 
int t;
ull dp[20][10],f[20],a[20];
void init()
{
	dp[2][4]=1;f[0]=1;
	for(int i=1;i<=18;++i)f[i]=f[i-1]*10;
	for(int i=3;i<=18;++i)
	{
		for(int j=0;j<=9;++j)//49 149 249 349 449 490-499 
		{
			for(int k=0;k<=9;++k)
			{
				if(j==4&&k==9)dp[i][j]+=f[i-2]; 
				else dp[i][j]+=dp[i-1][k];
			} 
			//高位4 次高位9 剩下随便取
			//4900-4999会将4949重复计算 故不加 单独计算 
			//printf("%lld到%lld共有%lld个\n",j*f[i-1],(j+1)*f[i-1]-1,dp[i][j]); 
		}
	} 
}
ull cal(ull x)
{
	ull ans=0;
	for(int i=0;i<=19;++i)a[i]=0;
	while(x)
	{
		a[++a[0]]=x%10;
		x/=10;
    }
	for(int i=a[0];i>=1;--i)//从高到低统计
	{
		for(int j=0;j<a[i];j++)
		{
			ans+=dp[i][j];
		}
		if(a[i+1]==4&&a[i]==9)//后面的会受到前缀49的影响 直接一并统计然后跳出来 
		{
			ull res=0;
			for(int j=i-1;j>=1;j--)
			res=res*10+a[j];
			ans+=res; 
			break;
		}
	} 
	return ans;
}
int main()
{
	init();
	scanf("%d",&t);
	while(t--)
	{
		ull x;
		scanf("%llu",&x);
		printf("%llu\n",cal(x+1));
	}
	return 0;
}

代码3(后记:20190504)

自己又瞎搞了一个划分

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
int t;
ull n;
ull dp[20][3];//dp[i][j]表示i位数的状态
//dp[i][0]:i位数不含49且最高位不是9 
//dp[i][1]:i位数不含49最高位是9
//dp[i][2]:i位数含49 
ull a[20];
void init()
{
	dp[0][0]=1;//视具体情况而定 
	//dp[1][0]到dp[1][9]都得从dp[0][0]里取状态 故赋1 
	for(int i=1;i<=19;++i) 
	{
		dp[i][0]=dp[i-1][0]*9+dp[i-1][1]*8;
		dp[i][1]=dp[i-1][0]+dp[i-1][1];
		dp[i][2]=10*dp[i-1][2]+dp[i-1][1]; 
	}
}
ull solve(ull x)
{
	ull ans=a[0]=0;
	for(;x;x/=10)
	a[++a[0]]=x%10;
	a[++a[0]]=0;//高位补0
	bool flag=0;//是否已经出现49 
	for(int i=a[0]-1;i>=1;--i)//i是几位数 
	{ 
	  ans+=a[i]*dp[i-1][2];
	  if(flag)ans+=a[i]*(dp[i-1][0]+dp[i-1][1]);//[0,a[i]-1]
	  else if(a[i]>4)ans+=dp[i-1][1];
	  if(a[i+1]==4&&a[i]==9)flag=1;
	} 
	return ans;
}
int main()
{
	init();
	scanf("%d",&t);
	while(t--)
	{
	scanf("%llu",&n);
	printf("%llu\n",solve(n+1));
    }
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值