洛谷 P2567 [SCOI2010]幸运数字 题解

主要思路

显然我们需要求出所有范围内的合法幸运号码,然后容斥。

容斥就是暴搜+剪枝。

剪枝&优化

  1. 一个幸运号码是另一个幸运号码的倍数,则这个数不会产生任何贡献,剪枝。
  2. 选定数的 LCM 大于 \(r\) 则直接退出。
  3. 将幸运号码从大到小排序,这样搜索的时候能更快突破上界。
  4. 对于所有大于 \(\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
 */
posted @ 2022-10-01 18:49  Day_Dreamer_D  阅读(333)  评论(0)    收藏  举报