DP-数位DP

概念

数位DP,字面意思就是在数位上进行dp。这种DP只和数组结构有关与数的大小没什么关系。
一般的题目是给你两个数,让你求这两个数之间符合条件的数的个数,且这两个数非常大,这样的题目一般就是数位DP 题。
(关键字:某个区间,对数xxx的操作/限制,且这个区间高达(1016左右的)数位)


例题:数字计数
题目描述:给定两个正整数 a和 b,求在 [a,b]中的所有整数中,每个数码(digit)各出现了多少次。
解题思路(从题解搬运):f[i]代表在有i位数字的情况下,每个数字有多少个。如果不考虑前导0,你会发现对于每一个数,它的数量都是相等的,也就是f[i]=f[i-1]*10+10(i-1);(10i-1是最高位出现i的次数,f[i-1]*10就是封顶(如9999)时候,f[i-1]要出现十次))

假如我们要求的数是ABCD,我们可以拆分成A000+BCD,
(1)对于A,A出现的次数是A·f[3](前面三位是满的,不在最高位,出现的次数就是f[3])+BCD+1(以A开头有这么多)。
(2)对于比A小的数,则是f[3]·A+103
(3)比A大的数,不变是103
然后我们在考虑0开头的数,就是cnt[0]都要减去10i-1+10i-2+……101
因为是求两个数之间的范围,所以我们用cnta[10]记录a-1中每个数出现的次数,用cntb[10]记录b中每个数出现的次数,然后对应位置相减就行了

#include<iostream>
#include<math.h>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
long long cnt1[20];//存a-1
long long cnt2[20];//存b
long long num[20];
long long f[20];
void solve(long long x,long long cnt[])
{
	long long num[20]={0};
	int len=0;//一共有len位
	while(x)
	{
		num[++len]=x%10;
		x=x/10;
	}
	for(int i=len;i>=1;i--)//前面是逆序存储的
	{
		for(int j=0;j<=9;j++)
		{
			cnt[j]+=f[i-1]*num[i];
		}
		for(int j=0;j<num[i];j++)//比A小的数都有+=10^(i-1)
		{
			cnt[j]+=pow(10,i-1);
		}
		long long sum=0;
		for(int j=i-1;j>=1;j--)//A这个数还要加上bcd+1
		{
			sum=sum*10+num[j];
		}
		sum++;
		cnt[num[i]]+=sum;
		cnt[0]-=pow(10,i-1);//注意以0开头的要减去	
	}
	//cnt[0]++;
}
int main()
{
	long long a,b;
	cin>>a>>b;
	f[0]=0;
	for(int i=1;i<=20;i++)
	{
		f[i]=f[i-1]*10+pow(10,i-1);
	}
	solve(a-1,cnt1);
	solve(b,cnt2);
	for(int i=0;i<=9;i++)
	{
		cout<<cnt2[i]-cnt1[i]<<" ";
	}
}

windy数
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,

在A和B之间,包括A和B,总共有多少个windy数?

#include<iostream>
#include<cstring>
#include<math.h>
#include<cmath>
using namespace std;
long long f[15][15];//f[i][j]表示到第i位的时候,最高位数是j
long long num[15];
void init()
{
	for(int i=0;i<=9;i++)
	{
		f[1][i]=1;
	}
	for(int i=2;i<=10;i++)
	{
		for(int j=0;j<=9;j++)//第i位,最高位数是j的时候
		{
			for(int k=0;k<=9;k++)
			{
				if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
			}
		}
	}
}
long long solve(long long x)
{
	memset(num,0,sizeof(num));
	int len=0;
	while(x)
	{
		num[++len]=x%10;
		x=x/10;
	}
	long long ans=0;
	for(int i=1;i<=len-1;i++)//先求小于len位的
	{
		for(int j=1;j<=9;j++)//注意这里是1,直接排除最高位=0的情况
		{
			ans+=f[i][j];
		}
	}
	for(int i=1;i<=num[len]-1;i++)//len位置但是最高位小于num[len]
	{
		ans+=f[len][i];
	}
	//求最高位是num[len]的
	for(int i=len-1;i>=1;i--)
	{
		for(int j=0;j<=num[i]-1;j++)//此时是i位数,这一位小于num[i]的情况。如果这一位等于num[i]再往下走,就是逐步分为各个小问题的过程
		{
			if(abs(j-num[i+1])>=2){ ans+=f[i][j];}
		}
		if(abs(num[i]-num[i+1])<2) break;
	}
	return ans;
}
int main()
{
	init();
	long long a;
	long long b;
	cin>>a>>b;
	long long bw=solve(b+1);//不会算我们输进去的那个数而只会算到输进去的那个数-1
	long long aw=solve(a);
	cout<<bw-aw<<endl;
}

总结起来我们可以知道:我们首先做初始化,即每一位上的最高位是j时候我们dp[i][j]应该存什么。然后在进行dp时,当最高位这一位(len位)没有到达我们存入的num[len],就可以用到前面的dp。然后我们在将最高位是这一位单独拿出来讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值