luogu3172 [CQOI2015]选数 莫比乌斯反演+杜教筛

link

题目大意:有N个数,每个数都在区间[L,H]之间,请求出所有数的gcd恰好为K的方案数

推式子

首先可以把[L,H]之间的数字gcd恰好为K转化为[(L-1)/K+1,H/K]之间数字gcd恰好为1

然后就可以反演了

下面手误把所有的H都打成了R

\(\sum_{i_1=L}^R\sum_{i_2=L}^R\dots\sum_{i_N=L}^R[\gcd(i_1,i_2,\dots,i_N)=1]\)

\(\sum_{i_1=L}^R\sum_{i_2=L}^R\dots\sum_{i_N=L}^R\sum_{d|i_1,d|i_2,\dots,d|i_N}\mu(d)\)

\(\sum_{d=1}^R\mu(d)\left(\frac{R}d-\frac{L-1}d\right)^N\)

显然可以打数论分块

但是这道题H范围达到10的9次方,果断杜教筛

注意由于我们的d是枚举到R的(因为右边式子的关系不是相乘,而是相减,所以大于L的项也有效)而>=L的数整除L-1的值为0,所以会导致除法爆炸,所以需要特判,详细请见代码

#include <cstdio>
#include <utility>
#include <map>
using namespace std;

int p = 1000000007, fuck = 2000000;
int n, k, l, h;
bool vis[2000010];
int prime[2000000], tot, mu[2000010];
map<int, int> memory;

int qpow(int x, int y)
{
	int res = 1;
	for (x %= p; y > 0; y >>= 1, x = x * (long long)x % p) if (y & 1) res = res * (long long)x % p;
	return res;
}

int chumu(int x)
{
	if (x <= fuck) return mu[x];
	if (memory.count(x)) return memory[x];
	int res = 1;
	for (int i = 2, j; i <= x; i = j + 1)
	{
		j = x / (x / i);
		res -= (j - i + 1) * chumu(x / i);
	}
	return memory[x] = res;
}

int main()
{
	scanf("%d%d%d%d", &n, &k, &l, &h);
	h /= k, l = (l - 1) / k;
	mu[1] = 1;
	for (int i = 2; i <= fuck; i++)
	{
		if (vis[i] == false) prime[++tot] = i, mu[i] = -1;
		for (int j = 1; j <= tot && i * prime[j] <= fuck; j++)
		{
			vis[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
			mu[i * prime[j]] = -mu[i];
		}
		mu[i] += mu[i - 1];
	}
	int res = 0;
	for (int i = 1, j; i <= h; i = j + 1)
	{
		j = min(h / (h / i), l / i == 0 ? h : l / (l / i));
		res += qpow((h / i - l / i), n) * (long long)(chumu(j) - chumu(i - 1)) % p;
		res %= p;
		if (res < 0) res += p;
	}
	printf("%d\n", res);
	return 0;
}
posted @ 2019-01-26 13:15  ghj1222  阅读(111)  评论(0编辑  收藏  举报