(数学题)洛谷 P2567 SCOI2010 幸运数字 题解

题意

在中国,很多人都把 6 6 6 8 8 8 视为是幸运数字!lxhgww 也这样认为,于是他定义自己的“幸运号码”是十进制表示中只包含数字 6 6 6 8 8 8 的那些号码,比如 68 68 68 666 666 666 888 888 888 都是“幸运号码”!但是这种“幸运号码”总是太少了,比如在 [ 1 , 100 ] [1,100] [1,100] 的区间内就只有 6 6 6 个( 6 6 6 8 8 8 66 66 66 68 68 68 86 86 86 88 88 88),于是他又定义了一种“近似幸运号码”。lxhgww 规定,凡是“幸运号码”的倍数都是“近似幸运号码”,当然,任何的“幸运号码”也都是“近似幸运号码”,比如 12 12 12 16 16 16 666 666 666 都是“近似幸运号码”。

现在 lxhgww 想知道在一段闭区间 [ a , b ] [a, b] [a,b] 内,“近似幸运号码”的个数。

1 ≤ a ≤ b ≤ 1 0 10 1\le a\le b\le10^{10} 1ab1010

思路

组成的数字只有 6 6 6 8 8 8,发现幸运数字并不多大概只有 2000 2000 2000 个。因此先找 b b b 以内的幸运数字,用 dfs 绰绰有余,并把有倍数关系的幸运数字去重,只留下最小的那个(比如 8 , 88 , 888 8,88,888 8,88,888 只保留 8 8 8),放进数组 t t t

下面就要找近似幸运数了。做过小奥题的同学们都知道:

  • [ 1 , n ] [1,n] [1,n] 中,找 a , b , c a,b,c a,b,c 的倍数有几个,先分别 a , b , c a,b,c a,b,c 的倍数个数,总和记为 s 1 s_1 s1,然后减去分别找 l c m ( a , b ) , l c m ( b , c ) , l c m ( a , c ) lcm(a,b),lcm(b,c),lcm(a,c) lcm(a,b),lcm(b,c),lcm(a,c) 的倍数个数,总和为 s 2 s_2 s2,最后再加上 l c m ( a , b , c ) lcm(a,b,c) lcm(a,b,c) 的倍数个数 s 3 s_3 s3,就能算出答案。
  • [ l , r ] [l,r] [l,r] 内有多少个 k k k 的倍数,答案是 ⌊ r k ⌋ − ⌈ l k ⌉ + 1 \left\lfloor\frac{r}{k}\right\rfloor-\left \lceil \frac{l}{k}\right \rceil+1 krkl+1

其实就是 容斥定理 和 前缀思想。

对于第一个结论,形式化地书写就是:

考虑一个序列 { t t o t } \{t_{tot}\} {ttot},记 v a l c n t val_{cnt} valcnt 表示 { t t o t } \{t_{tot}\} {ttot} 中所有的“任意 c n t cnt cnt 个元素的最小公倍数”。令 z ( k ) = ⌊ b k ⌋ − ⌈ a k ⌉ + 1 z(k)=\left\lfloor\frac{b}{k}\right\rfloor-\left \lceil \frac{a}{k}\right \rceil+1 z(k)=kbka+1,那么: a n s = ∑ i = 1 t o t ( − 1 ) i z ( v a l i ) ans=\sum_{i=1}^{tot}(-1)^iz(val_i) ans=i=1tot(1)iz(vali)

考虑到 b ≤ 1 0 10 b\le 10^{10} b1010,算 l c m lcm lcm 的时候有相乘操作,开 long long 数据恶心点就爆,那就要用 long long 2:__int128 来定义。(就因为这个虚空调试了很久)

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
#define i8 __int128
const ll N=2e6+9;
ll a,b;
ll tot,n;
ll t[N],xy[N];
bool vis[N];
void dfs(ll x)
{
	if(x>b)return;
	if(x)xy[++n]=x;
	dfs(x*10+6);
	dfs(x*10+8);
}
bool cmp(ll x,ll y)
{
	return x>y;
}
ll ans;
i8 gcd(i8 x,i8 y)
{
	if(y==0)return x;
	if(x==0)return y;
	return gcd(y,x%y);
}
i8 lcm(i8 x,i8 y)
{
	if(x==0)return y;
	if(y==0)return x;
	return x*y/gcd(x,y);
}
void cal(ll id,ll cnt_lcm,i8 val)
{
	if(val>b)return;
	if(id>tot)
	{
		if(val)
		{
			ll qj=(ll)(cnt_lcm&1?1:-1);
			ans+=qj*(floor((dd)b/val)-ceil((dd)a/val)+1);
		}
		return;
	}
	cal(id+1,cnt_lcm+1,lcm(val,t[id]));
	cal(id+1,cnt_lcm,val);
}
int main()
{
	scanf("%lld%lld",&a,&b);
	dfs(0);
	sort(xy+1,xy+n+1);
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])t[++tot]=xy[i];
		for(int j=i+1;j<=n;j++)
		if(xy[j]%xy[i]==0)vis[j]=1;
	}
	sort(t+1,t+tot+1,cmp);
	cal(1,0,0);
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值