【SCOI2010】【容斥原理】幸运数字

这道题首先需要注意的是数据范围,两个10^10规模的数相乘会爆long long,所以要尽量避免乘法。

这道题很容易让人想到数位DP,不过这个想法是错误的,这就提示在考试中如果想到一种方法,但是却怎么也想不出来时不要盲目地陷入其中,而要冷静思考有没有其他的解决办法。

这个解决办法就是利用容斥原理。

先dfs出所有的“真正的”幸运数字,然后再利用容斥原理找出近似幸运数字

为了避免超时,需要一个剪,就是如果一个幸运数字是一个比他小的幸运数字的倍数,则这个幸运数字不用考虑,因为后面会统计到。

把处理过的数字从大到小排序,这样可以避免TLE,因为大的数字开始更有可能大于limit从而跳出dfs。

根据容斥原理,答案就为:每个数的倍数个数-每两个数的公倍数个数+每三个数...这个可以通过传递数的个数来判断。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 3000 + 10;
bool flag[maxn];
long long luck[maxn];
long long cnt = 0,ret = 0;
long long limit,a,b;
void init()
{
	freopen("bzoj1853.in","r",stdin);
	freopen("bzoj1853.out","w",stdout);
}

bool cmp(const long long &a,const long long &b)
{
	return a > b;
}

void dfs(long long num)
{
	if(num > limit)return;
	if(num != 0)luck[cnt++] = num;
	dfs(num * 10 + 6);
	dfs(num * 10 + 8);
}

void readdata()
{
	scanf("%lld%lld",&a,&b);
}

long long gcd(long long a,long long b)
{
	long long t;
	while(b != 0)
	{
		t = a % b;
		a = b;
		b = t;
	}
	return a;
}

void work(int step,long long lcm,int tot)
{
	if(step == cnt)
	{
		if(tot)
		{
			long long tmp = limit / lcm;
			if(tot&1)ret += tmp;
			else ret -= tmp;
		}
		return;
	}
	for(int i = step;i < cnt;i++)
	{
		long long tmp = gcd(lcm,luck[step]);
		if((double)lcm / tmp > (double)limit / luck[step])return;
		tmp = lcm * luck[step] / tmp;
		work(i + 1,tmp,tot + 1);
	}
}

long long calc(long long x)
{
	if(x < 6)return 0;
	ret = 0,cnt = 0;
	limit = x;
	dfs(0);
	stable_sort(luck,luck + cnt);
	for(int i = 0;i < cnt;i++)
	{
		flag[i] = true;
		for(int j = 0;j < i;j++)
		{
			if(luck[i] % luck[j] == 0)flag[i] = false;
		}
	}
	int tmp = cnt;
	cnt = 0;
	for(int i = 0;i < tmp;i++)
	{
		if(flag[i])luck[cnt++] = luck[i];
	}
	stable_sort(luck,luck + cnt,cmp);
	for(int i = 0;i < cnt;i++)
	{
		work(i,1,0);
	}
	return ret;
}

void solve()
{
	long long ans = calc(b) - calc(a - 1);
	printf("%lld",ans);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}


你可能感兴趣的:(【SCOI2010】【容斥原理】幸运数字)