【数位DP】【SCOI2009】windy数

传送门

Description

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

AB之间,包括AB,总共有多少个windy数?

Input

包含两个整数,A,B

Output

一个整数

Sample Input

25 50

Sample Output

20

Hint

For All:

1  A  B  2 × 109

Solution

数位DP。

数位DP的DP状态一般含有如下参数:

1、从高到低当前填到了第几位。
2、当前这一位的数是几。
3、这一位是否等于一个上界x。一般而言,大于上界没有意义,所以可以用一个二进制表示等于或小于。
4、从最高位到这一位是否全为0

对于具体题目,需要根据要求增删状态。

对于本题而言,可以设fi,j,0/1,0/1代表从高到低填到了第i位,当前这一位数字是j,否/是等于0,否/是全为0的方案数

考虑[A,B]间的答案就等于小于B的答案减去小于A1的答案。于是可以分别把BA1作为上界x求得答案相减。

预处理:处理出第一位所有的情况。具体的,设第一位是s1,则f1,j,0,0=1|j<s1,f1,0,0,1=1,f1,s1,1,0=1

转移方面,这里使用刷表法刷出下一维。具体的,枚举当前是什么状态,枚举下一位的数字是谁。

直接累加小于上界且从小于上界的状态转移的答案,对于等于上界的状态,转移到等于上界的状态。注意区分小于和等于在第3维上的差异。

显然每一位全是前导零的方案数是1。对于本位置全是前导0的方案,可以更新下一位填任意位置小于上界的状态。

最后累加答案为ans=(j<slenflen,j,0,0)+flen,slen,1,0。其中len为上界x的位数。

看着发晕的话可以参考代码

Code

#include<cstdio>
#include<cstring> 
#define rg register
#define ci const int
#define cl const long long int

typedef long long int ll;

namespace IO {
    char buf[90];
}

template<typename T>
inline void qr(T &x) {
    char ch=getchar(),lst=' ';
    while(ch>'9'||ch<'0') lst=ch,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(lst=='-') x=-x;
}

template<typename T>
inline void write(T x,const char aft,const bool pt) {
    if(x<0) x=-x,putchar('-');
    int top=0;
    do {
        IO::buf[++top]=x%10+'0';
        x/=10;
    } while(x);
    while(top) putchar(IO::buf[top--]);
    if(pt) putchar(aft);
}

template<typename T>
inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
template<typename T>
inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
template<typename T>
inline T mabs(const T a) {if(a<0) return -a;return a;}

template<typename T>
inline void mswap(T &a,T &b) {
    T temp=a;a=b;b=temp;
}

const int maxs = 15;

ll a,b;
ll frog[maxs][maxs][5][5],st[maxs];

ll dp(ll x);

int main() {
	qr(a);qr(b);
	a=dp(a-1);memset(frog,0,sizeof frog);b=dp(b);
	write(b-a,'\n',true);
	return 0;
}

ll dp(ll x) {
	int len=0;
	rg ll tx=x;
	do {++len;} while(tx/=10);if(!x) len=0;						//确定x的位数 
	for(rg int i=len;i;--i) st[i]=x%10,x/=10;					//st[i]即为s数组,存储x每一位的值 
	for(rg int i=1;i<st[1];++i) frog[1][i][0][0]=1;
	frog[1][st[1]][1][0]=frog[1][0][0][1]=1;							
	/*
		*初始化:
		*第一位填小于s[1]的数,整个数小于x前1位且无前导0的方案数
		*=第一位填s[1]的数,整个数等于x前1位的方案数
		*=全部填0的方案数=1 
	*/
	for(rg int i=1;i<len;++i) {
		rg int di=i+1;											//下一位置 
		for(rg int j=0;j<10;++j) {
			for(rg int k=0;k<10;++k) {
				if(mabs(j-k) >= 2) {							//如果这一位合法 
					frog[di][k][0][0]+=frog[i][j][0][0];		//这一位填k的方案数,从上一位的数小于x转移 
					if(k < st[di]) frog[di][k][0][0]+=frog[i][j][1][0];
																//从上一位等于x的数转移到这一位小于x的答案 
					else if(k == st[di]) frog[di][k][1][0]+=frog[i][j][1][0];
																//从上一位等于x的数转移到这一位等于x的答案 
				}
			}
		}
		frog[di][0][0][1]=1;									//下一位全是前导0的方案数为1 
		for(rg int j=1;j<10;++j) frog[di][j][0][0]+=frog[i][0][0][1];
																//由这一位全是前导0可以更新下一位的不取0的所有情况。 
	}
	ll _ans=0;
	for(rg int i=0;i<10;++i) _ans+=frog[len][i][0][0];
	_ans+=frog[len][st[len]][1][0];
	return _ans;
}

Summary

数位dp状态的确定:

1、从高到低当前填到了第几位。
2、当前这一位的数是几。
3、这一位是否等于一个上界x。一般而言,大于上界没有意义,所以可以用一个二进制表示等于或小于。
4、从最高位到这一位是否全为0

转移细节比较多,需要留心

posted @   一扶苏一  阅读(290)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示