20250922 数位DP总结

引子

我不想写总结,我不想写数位DP,我不想写数位DP总结……

笑死了,上次一道数位DP(n≤1012)(n≤10^{12})(n1012)的题,一个蒟蒻说:“st都要大炮打?”然后O(n)。

一句实在话,这篇文章写的很烂,也就只有代码能看了。

数位DP

数位大炮,顾名思义就是用来打表的,不要疑惑,本来就是!

要使用数位DP的题目通常是求一个区间内有几个符合条件的数,一般和每个数上的每一位有关系,就比如求一个数的每一位的数的和。

洛谷-P13085 windy 数(加强版)

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共有多少个 windy 数?(1≤a≤b≤1018)//(1≤a≤b≤10^{18})//(1ab1018)//没啦

那么DP数组怎么设计呢?我们可以把dp[i][j]dp[i][j]dp[i][j]设为一个iii位数当最高位是jjj时符合条件的数。那么最终的答案就是1到 b+1 的 windy 数的数量减去1到 a 的 windy 数的数量。

我们可以用一个函数表示 1 到 x 的 windy 数的数量。首先我们可以把它分成三部分。

就比如 789 ,第一部分为 1 ~ 9 和 10 ~ 99 的 windy 数的数量总和,用 dp 数组来表示就是 ∑dp[1][j]+∑dp[2][j](0<j<10)\sum dp[1][j]+\sum dp[2][j](0<j<10)dp[1][j]+dp[2][j](0<j<10);第二部分为 100 ~ 699 的 windy 数的数量,用 dp 数组来表示就是 ∑dp[3][j](0<j<7)\sum dp[3][j](0<j<7)dp[3][j](0<j<7);第三部分为 700 ~ 789 的 windy 数的数量,详细去代码里体会。

时间复杂度O(2e3)O(2e3)O(2e3)

#include<bits/stdc++.h>
#define int long long//懒得打long long,好吧,已经打了两个了
using namespace std;
int dp[20][20],a[20];//dp[i][j]表示一个i位数最高位是j的windy数数量
int csk(int x){//乱取的
	int len=0,ans=0;
	while(x>0){
		a[++len]=x%10;//用一个数组把这个数的每一位存起来,方便操作
		x/=10;
	}
	for(int i=1;i<len;i++){//第一部分,枚举是几位数
		for(int j=1;j<10;j++){//枚举i位数的最高位
			ans+=dp[i][j];
		}
	}
	for(int i=1;i<a[len];i++){//第二部分,枚举len位数的最高位
		ans+=dp[len][i];
	}
	for(int i=len-1;i>=1;i--){//第三部分,枚举每一位
		for(int j=0;j<a[i];j++){//枚举小于这位数的每一种可能
			if(abs(j-a[i+1])>=2){//如果j与前面一位的差值小于2
				ans+=dp[i][j];//加上
			}
		}
		if(abs(a[i+1]-a[i])<2){//用来省时,意思是当这一位和前面那一位的差小于2,那么后面的数肯定也不符合要求,所以可以直接去世
			break;
		}
	}
	return ans;//返回
}
signed main(){
	for(int i=0;i<9;i++){//初始状态
		dp[1][i]=1;//一位数只可能有一个windy数
	}
	for(int i=2;i<=19;i++){//几位数
		for(int j=0;j<10;j++){//i位数最高位为j
			for(int k=0;k<10;k++){//枚举i-1位的最高位
				if(abs(j-k)>=2){//如果他们的差值小于2
					dp[i][j]+=dp[i-1][k];//加上
				}
			}
		}
	}
	int x,y;
	cin>>x>>y;
	cout<<csk(y+1)-csk(x);//前缀和
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值