洛谷 P2567 [SCOI2010]幸运数字 题解
主要思路
显然我们需要求出所有范围内的合法幸运号码,然后容斥。
容斥就是暴搜+剪枝。
剪枝&优化
- 一个幸运号码是另一个幸运号码的倍数,则这个数不会产生任何贡献,剪枝。
- 选定数的 LCM 大于 \(r\) 则直接退出。
- 将幸运号码从大到小排序,这样搜索的时候能更快突破上界。
- 对于所有大于 \(\dfrac{r}{3}\) 的幸运号码,不能与其他任何数字合并,于是我们直接预先算出这些数字,对剩余部分暴搜。
代码
//P2567 [SCOI2010]幸运数字
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MOD=1e9+7;
int cnt;
ll a[5050],res,l,r;
bool vis[5050];
ll math_GCD(ll x,ll y)
{
return !y?x:math_GCD(y,x%y);
}
void _DFS(ll x)//不开 long long 就会 RE
{
if(x>r)
{
return;
}
if(x)
{
a[++cnt]=x;
}
_DFS(x*10+6);
_DFS(x*10+8);
return;
}
bool _check(ll a,ll b)//大于则直接剪枝
{
ll x=a/r,y=b/r;
if(x*y)
{
return true;
}
return x*y>r;
}
void _calc(int x,ll sum,int count)
{
ll d;//不开 long long 就会 RE
if(sum>r)//超过上界则直接返回
{
return;
}
if(x>cnt)
{
if(sum!=1)
{
if(count&1)
{
res+=r/sum-l/sum;
}
else
{
res-=r/sum-l/sum;
}
}
return;
}
_calc(x+1,sum,count);
d=a[x]/math_GCD(sum,a[x]);
if(!_check(sum,d))
{
_calc(x+1,sum*d,count+1);
}
return;
}
signed main()
{
int temp=0;
scanf("%lld%lld",&l,&r);
l--;
_DFS(0);
sort(&a[1],&a[cnt+1]);
for(int i=1;i<=cnt;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]%a[j]==0)//若为倍数,没有贡献,剪枝
{
vis[i]=true;
break;
}
}
}
for(int i=1;i<=cnt;i++)
{
if(!vis[i])
{
if(a[i]<=r/3)
{
a[++temp]=a[i];
}
else
{
res+=r/a[i]-l/a[i];
}
}
}
cnt=temp;
reverse(&a[1],&a[cnt+1]);
_calc(1,1,0);
printf("%lld\n",res);
return 0;
}
/*
* 洛谷
* https://www.luogu.com.cn/problem/P2567
* C++20 -O0
* 2020.10.1
*/

浙公网安备 33010602011771号