TYZ 8/25 送分题

送分题

【问题描述】

每个人都会枚举,所以给大家一个送分题。

区间[L,R]内有多少个数字K满足:

K各个非0位都能被K整除?

例如,250的非0位是25250/2=125,250/5=50

【输入格式】

一行,两个正整数L,R

【输出格式】

一个数字,表示答案的个数。

【输入样例】

12 15

【输出样例】

2

【数据范围与约定】

对于30%的数据,L,R<=10^5

对于60%的数据,L,R<=9*10^9

对于100%的数据,L,R<=10^18

  

确实送了分,

前30%的数据一年之前,甚至于刚刚开始学oi的时候就可以写出来。

  模拟,这个分解位数可以直接取模

  60%的数据

万能的打表!

  NOIP限制的提交源程序的体积在1MB以内,所以把10^10以内的数据全部打出来是不现实的,

然而我们可以在1s之内计算出10^6以内的数据(或者更多)

那么为何不隔10^6打一个表呢?

这个10^10的数据,我试过,在机子上跑大概25分钟就完了

用文件输出,每个数字中间各一个逗号,之后直接用到数组里面,

这样子60%的数据,输入之后先用除法和取模找到最近的那个表中的数据,然后直接进行有限次计算就OK了

分段打表!!!

索了这么多,但是由于Azuremayfly家里的电脑发热严重,所以表没打完

所以大家自行脑补一下这个程序吧,或者有时间到了配置I5的伟大TYZ机房,我再贡献出来这个表

NOIP考试的时候机子很好的

 去年SN在全国闻名的西安友谊西路上最有名的学校考的,机子的内存,CPU什么的这个表还是可以搞定的

但是还是要写一段代码 

/*
	
	假装有打表的样子
	假装watch[i]表示i*10^6的结果
	ll r,ll l;
	ll lans=watch[l/(1000000)];
	ll rans=watch[r/1000000];
	for(ll i= l/(1000000);i<=l;i++)
	if(check(i)) lans++;
	for(ll i= r/(1000000);i<=r;i++)
	if(check(i)) rans++;
	cout<<(rans-lans)<<endl;
	return 0;
	*/

来说满分做法

这是一个DP\记忆搜索

 因为0123456789这几个的最小公倍数就2520,他们各个数的最小公倍数也不过50来种

所以就有搞头了

 不要把数字看成一个大数字,就看成一个字符串或者一组数

 在代码里面用了num数组,这样你就可以看到,巨大的数字也不过20来个

 [L,R]范围内,有多少个数字,可以整除各个位上的数字的最小公倍数。
又因为,所有最小公倍数gcd肯定都是2520的约数,所以一个数字X%gcd一定等于X%2520%gcd。

dp[当前长度][对2520%的结果][当前最小公倍数(为了防止mle用了哈希)】

  

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
ll l,r;
ll ans;
ll t;
int a[25],ta;
bool check(ll x)
{
	ta=0; 
	int nx=x;
	while(x>0)
	{
		int nw=x%10;
		x/=10;
		a[ta]=nw;
		ta++;
	}
	for(int i=0;i<ta;i++)
	{
		if(a[i]!=0)
		{
			if(nx%a[i]!=0)
			return 0;
		}
	}
	return 1;
}
ll dp[25][2525][52];
ll hashs[2525],top_hashs;
ll gcd(ll xxx, ll yyy)
{
	if(!yyy)
	return xxx;
	else return gcd(yyy,xxx%yyy);
 } 
 ll lcm(ll xxx,ll yyy)
 {
 	ll res=xxx*yyy/gcd(xxx,yyy);
 	return res;
 }
ll num[25];//stands for the number of each position of the ll number 
ll top_num;
ll dfs(ll len_,ll mod_,ll lcm_,bool limit_)
{
	if(len_==-1)
	{
		if(mod_%lcm_)
		return 0;
		else
		return 1;
	}
	ll res=dp[len_][mod_][hashs[lcm_]];
	if(limit_==0&&res!=-1) return res;
	res=0;
	ll lm;
	if(limit_) lm=num[len_];else lm=9;
	res+=dfs(len_-1,(mod_*10)%2520,lcm_,0);
	for(int i=1;i<=lm;i++)
	{
		res+=dfs(len_-1,(mod_*10+i)%2520,lcm(lcm_,i),limit_&&i==lm);
	}
	if(!limit_)
	dp[len_][mod_][hashs[lcm_]]=res; 
	return res;
}
ll solve(ll obj)
{
	memset(num,0,sizeof(num));
	top_num=0;
	while(obj>0)
	{
		ll nw=obj%10;
		obj/=10;
		num[top_num]=nw,top_num++;
		//works like the stack		
	}
	return dfs(top_num-1,0,1,1);
}
int main()
{
	cin>>l>>r;
	if(l<=100000&&r<=100000)
	{
		for(ll i=l;i<=r;i++)
		{
			if(check(i))
			ans++;
		}
		cout<<ans<<endl;
		return 0;
	}
	
	else
	{
		memset(dp,-1,sizeof dp);
		int cnt = 0;
    for (int i = 1; i <= 2520; i++)
        if (2520 % i == 0) hashs[i] = cnt++;
        //用50以内的数字来表示 最小公倍数,方便数位dp;
		 ll ans=solve(r)-solve(l-1);
		 cout<<ans<<endl;
		 return 0;	  
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值