BZOJ 1026 windy数 (数位DP)

时间:2022-12-18 12:02:31

题目链接:BZOJ 1026

我开始是用记忆化搜索写的,然后前导零处理挂了。然后改用数位DP的一般形式写的。写数位DP的一般过程可以总结为先预处理符合条件的没有限制的数,然后将小于限制的直接相加,然后再处理边界上的数(边界处理各种蛋疼)。数位DP的一个重要的思想就是逐位计算。

两篇比较好的国家集训队论文:浅谈数位统计类问题            数位计数问题解法研究

然后是我的代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;

int base[20],f[20][20],dp[20][20];

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

void init(){
	base[1]=1;//每一位的位权
	for(int i=2;i<=10;i++)base[i]=base[i-1]*10;
	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++)
			for(int k=0;k<=9;k++)
				if(abs((double)j-k)>=2)f[i][j]+=f[i-1][k];
}

int find(int x){
	if(!x)return 0;
	int t=0,w=10;
	while(base[w]>x)w--;//找到x是几位数
	for(int i=1;i<w;i++)//小于该位的没有前导零的方案数直接相加
		for(int j=1;j<=9;j++)
			t+=f[i][j];
//////////////////////////////////////////////////////////////
//考虑是w位数的情况
	int limit=x/base[w];//limit为该位的限制
	for(int i=1;i<limit;i++)t+=f[w][i];
	x%=base[w];
	int pre=limit;
	for(int i=w-1;i>=1;i--){
		limit=x/base[i];
		if(i!=1){
			for(int j=0;j<limit;j++)
				if(abs((double)pre-j)>=2)t+=f[i][j];
		}
		else{
			for(int j=0;j<=limit;j++)
				if(abs((double)pre-j)>=2)t+=f[i][j];
		}
		if(abs((double)pre-limit)<2)break;//不合限制直接退出循环防止多算
		x%=base[i];
		pre=limit;
	}
	return t;
}

int main(){
	init();
	int A=read(),B=read();
	printf("%d\n",find(B)-find(A-1));
	return 0;
}

记忆化搜索写的先挖个坑之后再填吧。。。