衡阳八中1026windy数解题报告

1026: [SCOI2009]windy数

Description

windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

Input

包含两个整数,A B。

Output

一个整数。

Sample Input

【输入样例一】
1 10

【输入样例二】
25 50

Sample Output

【输出样例一】
9

【输出样例二】
20

【数据规模和约定】
20%的数据,满足 1 < = A <= B <= 1000000 。
100%的数据,满足 1 < = A <= B <= 2000000000 。
解题报告和代码都在code里:
/*
	如果没有看范神的代码的话,我会因为上下界的处理写一个很繁的代码,比如昨天比赛的那几道题。经过范神代码的启发,加之一个小修改优化了一维的时间复杂度。
	思路如下:
		先预处理出dp,dp[i][j]表示长度为i+1,并且第一个数字为j的总数。
		之后对于每组数据[a,b],用[0,b]里的总数-[1,a-1]里的总数。
		所以接下来的任务就是求[0,n]里的总数,程序里find函数实现。[0,n]可以等价转化为[0,n+1),这个转化在下面可以看到其巧妙之处。
		find实现过程:
			find(n)求的是[0,n)里的总数
			设n各位分别为a0 a1 a2... ak ( a0 > 0 )
			则可以拆解为求以下几个范围的总数的和:
				[ 0  0  0  0...0, a0  0  0  0... 0)
				[a0  0  0  0...0, a0 a1  0  0... 0)
                [a0 a1  0  0...0, a0 a1 a2  0... 0)
				[a0 a1 a2  0...0, a0 a1 a2 a3... 0)
				...
				[a0 a1 a2 a3...0, a0 a1 a2 a3...ak)
			观察每个区间,前缀固定后,后缀都是某个满区间,比如第二行,a0固定后,只用求后缀[0,a1 0 0 0..0),而这个样子又和第一行相似
								      比如第三行,a0,a1固定后,只用求后缀[0,a2 0 0 0...0),这个样子和第一行相似
			于是乎,处理这些区间都是一个相似的过程:
				由前缀推导出现在的状态(设前缀为a0 a1 a2 ... ai),然后加上dp[k-i][j],0 <= j < ai+1,j还要符合题意里的约束条件。
			此时只需要两层循环就可以求出结果了。
			要注意的是,某个前缀已经不符合条件时,之后的区间就不用枚举了。
			另外在求第一行那个区间时,由于最高位非0的问题,不能dp[i][0]了事,而要枚举dp[i][1..9]的和。
		代码如下:
*/
#include <cstdio>
#include <numeric>
#include <cstring>
using namespace std;

int dp[15][10];
int abs(int a)
{
	return a<0?-a:a;
}
//dp预处理出来
void DP()
{
	memset(dp,0,sizeof(dp));
	for(int i = 0 ; i < 10 ; i++ )
		dp[0][i] = 1;
	for(int i = 1 ; i < 10 ; i++ )
		for(int j = 0 ; j < 10 ; j++)
			for(int k = 0 ; k < 10 ; k++)
				if(abs(j-k)>1)dp[i][j]+=dp[i-1][k];
}
int find(int n)
{
	int ans = 0;
	char s[15];
	sprintf(s,"%d",n);
	int len = strlen(s);
	//对于第一行区间的求解
	for(int i = 0; s[i+1]; i++)
		ans += accumulate(dp[i]+1,dp[i]+10,0);
	//对于第二行直到最后的求解,i的条件判断是对前缀的判断,j的起点也根据是否是第一位而有所不同
	for(int i = 0; s[i] && (i<2 || abs(s[i-1]-s[i-2])>1); i++)
		for(int j = !i ? 1 : 0 ; j < s[i]-'0' ; j++)
			ans += dp[len-i-1][j] * ( !i || (abs(j-s[i-1]+'0')>1));
	return ans;
}	


int main()
{
	int a,b;
	DP();
	while(scanf("%d %d",&a,&b)==2)
	{
		printf("%d\n",find(b+1)-find(a));
	}
	return 0;
}
//此题的思路是hdu3565bi-PeakNumber的前导,这个思想只是处理了上界,同理可以拓展到处理下界

进阶题目: hdu3565 Bi-peak Number结题报告
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值