【OI做题记录】【BZOJ】【SCOI2009】windy数

13 篇文章 0 订阅
3 篇文章 0 订阅

试题编号:BZOJ1026

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

输入描述

包含两个整数,A B。

输出描述

 一个整数

输入样例

【输入样例一】
1 10
【输入样例二】

25 50

输出样例

【输出样例一】
9
【输出样例二】
20


【数据规模和约定】

100%的数据,满足 1 <= A <= B <= 2000000000 。


试题分析

很明显强行枚举时间复杂度为O(2*10^10)绝对是超时的(按每个数10位算)

我们就必须用优化了:我们发现,对于一个长度为len的windy数个数的话,他是包含了部分长度为len-1的windy数个数的。换一种说法,一个长度len的windy数,后面len-1位也是windy数。而且我们明明已经求出后len-1位是windy数,还要傻傻地将整个长度为len的windy数看一次。

这又是重复子问题,又是DP。而且是数位DP

数位DP并不好想,也不好看出来。我们经常要将一个数拆开来就出子问题。


做法:

用一个f数组存储,f[i][j]表示长度为i,最高位是j的windy数个数。

我们可以推出方程

f[i][j]+=f[i-1][k];(abs(k-j)>=2)

先做一次预处理再说:

void restart()
{
	memset(f,0,sizeof(f));
	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)位的数 
	for(int k=0;k<=9;k++)//i-1位的数 
	{
	if(abs(k-j)>=2)f[i][j]+=f[i-1][k];
	} 
}

然后呢,假如我有一个方法求出不大于k的所有windy数个数,则答案为:

done(y+1)-done(x);

接下来讲讲怎么求不大于k的windy数,可以分成两类

1、位数小于k的所有windy数

2、位数等于k的所有windy数

其中第二类:

假设最高位不相同,则求出所有最高位小于k最高位的windy数;

假设最高位相同,次高位不相同,则求出所有最高位小于k次高位的、长度为len(k)-1的windy数;

以此类推。


代码

不给你看

#include<cstdio>
#include<cstring>
int st,ed;
int f[15][10];
int abs(int x)
{
	return x<0?(-x):x;
}
void restart()
{
	memset(f,0,sizeof(f));
	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)位的数 
	for(int k=0;k<=9;k++)//i-1位的数 
	{
	if(abs(k-j)>=2)f[i][j]+=f[i-1][k];
	} 
}
int done(int x)
{
	int soy=x;
	int s[15],len=0;
	while(soy!=0){
		len++;
		s[len]=soy%10;
		soy/=10;
	}
	int ans=0;
	for(int i=1;i<len;i++)
	for(int j=1;j<=9;j++)
	ans+=f[i][j];
	
	for(int i=1;i<s[len];i++)ans+=f[len][i];
	
	for(int i=len-1;i>=1;i--)//此时保证len~i+1位相等 
	{
		for(int j=0;j<s[i];j++) 
		if(abs(j-s[i+1])>=2)ans+=f[i][j];
		if(abs(s[i]-s[i+1])<2)break;
	}
	return ans;
}
int main()
{
	
	
	restart();
	/*for(int i=1;i<=10;i++)
	{
	for(int j=0;j<=9;j++)
	printf("%d ",f[i][j]);
	printf("\n");
	}*/
	scanf("%d %d",&st,&ed);
	printf("%d\n",done(ed+1)-done(st));
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值