莫比乌斯反演的形式:
另一种描述是:
一种是和所有的约数有关一种是和所有的倍数有关,解题的时候要根据题目选择合适的表达形式,感觉第二种用的比较多。
关于莫比乌斯函数mu,他的定义如下:
这个莫比乌斯函数有一些性质:
(1)
(2)
一般需要预处理所有的莫比乌斯函数值,需要用到线性筛
mu[1] = 1; for (int i = 2; i <= maxn; i++) { if (!vis[i]) { prime[cnt++] = i; mu[i] = -1; } for (int j = 0; j < cnt; j++) { if (i*prime[j] > maxn) break; vis[i*prime[j]] = 1; if (i%prime[j] == 0) { mu[i*prime[j]] = 0; break; } else { mu[i*prime[j]] = -mu[i]; } } }
HDU1695:点击打开链接
题意是[1,n],[1,m]两个区间中有多少对数的gcd是k。
先把题目转化为[1,n/k],[1,m/k]两个区间中有多少对数互质,可以用容斥原理求出所有gcd不为1的对数求出结果,但是用莫比乌斯反演可以更加高效的求解。
假设f(x)表示gcd为x的对数,F(x)表示gcd为x倍数的对数,那么显然f(x)和F(x)满足莫比乌斯反演的第二种描述,我们需要求出f(1):
f(1)=mu(1)F(1)+mu(2)F(2)+mu(3)F(3)+....,显然对于区间[1,a],[1,b]有F(x)=(a/x)*(b/x),所以到x=min(a,b)之后就不需要累计了因为F(x)始终为0。
然后因为题目规定了(a,b)和(b,a)算成一种,所以最终结果需要处理一下。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 111111 long long a, b, c, d, k; long long prime[maxn], cnt; bool vis[maxn]; int mu[maxn]; void get_mu () { cnt = 0; memset (vis, 0, sizeof vis); mu[1] = 1; for (long long i = 2; i <= 100000; i++) { if (!vis[i]) { prime[cnt++] = i; mu[i] = -1; } for (long long j = 0; j < cnt; j++) { if (i*prime[j] > 100000) break; vis[i*prime[j]] = 1; if (i%prime[j] == 0) { mu[i*prime[j]] = 0; break; } else { mu[i*prime[j]] = -mu[i]; } } } } long long solve (long long n, long long m) { long long ans = 0; for (long long i = 1; i <= n; i++) { ans += mu[i]*((long long)(n/i) * (long long)(m/i)); } return ans; } int main () { //freopen ("in.txt", "r", stdin); get_mu (); int t, kase = 0; scanf ("%d", &t); while (t--) { scanf ("%lld%lld%lld%lld%lld", &a, &b, &c, &d, &k); if (k == 0) { printf ("Case %d: 0\n", ++kase); continue; } b /= k; d /= k; if (b > d) swap (b, d); printf ("Case %d: %lld\n", ++kase, solve (b, d)-solve (b, b)/2); } return 0; }
BZOJ2301:点击打开链接
和上面那题很类似。
假设经过处理上下界以后是求出[a,b],[c,d]的互质对数,如果ans(x,y)表示[1,x],[1,y]的互质对数,那么根据容斥原理,也就是求出ans(b,d)+ans(a-1,c-1)-ans(a-1,d)-ans(c-1,b)。
但是数据组数很多如果继续使用遍历复杂度将会达到O(q*n)显然会爆炸,可以使用分块加速思想。
也就是需要Σ[n/i]中间结果会有很多相同的数,可以发现[i,n/(n/i)]之间的结果是相等的,也就是F(i)到
F(min(n/(n/i), m/(m/i)))之间的结果是相同的,所以可以先预处理mu函数的前缀和。复杂度变成了O(q*sqrt (n))。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; #define maxn 51111 long long a, b, c, d, k; long long prime[maxn], cnt; bool vis[maxn]; int mu[maxn]; long long sum[maxn]; void get_mu () { memset (vis, 0, sizeof vis); cnt = 0; mu[1] = 1; for (int i = 2; i <= 50000; i++) { if (!vis[i]) { prime[cnt++] = i; mu[i] = -1; } for (int j = 0; j < cnt; j++) { if (i*prime[j] > 50000) break; vis[i*prime[j]] = 1; if (i%prime[j] == 0) { mu[i*prime[j]] = 0; break; } else { mu[i*prime[j]] = -mu[i]; } } } sum[0] = 0; for (int i = 1; i <= 50000; i++) sum[i] = sum[i-1]+mu[i]; } long long solve (long long a, long long b) { long long ans = 0; if (a > b) swap (a, b); long long j; for (long long i = 1; i <= a; i = j+1) { j = min (a/(a/i), b/(b/i)); ans += (sum[j]-sum[i-1])*(long long)(a/i)*(long long)(b/i); } return ans; } int main () { get_mu (); int t; scanf ("%d", &t); while (t--) { scanf ("%lld%lld%lld%lld%lld", &a, &b, &c, &d, &k); b /= k, d /= k; a = ceil (a*1.0/k), c = ceil (c*1.0/k); printf ("%lld\n", solve (b, d) + solve (a-1, c-1) - solve (a-1, d) - solve (c-1, b)); } return 0; }
题意:在区间[1,a][1,b]中有多少对数的gcd满足:他们的gcd分解后素因子数小于等于p。
同样的假设f(x)表示gcd为x的对数,F(x)表示gcd为x倍数的对数,可以有:
于是就可以对于每一个i,求出这个i在自己的质因子数下对F(x)的贡献,然后通过前缀和和分块加速,和上题类似的做法就可以在O(q*sqrt(n))时间内求出结果。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> #include <vector> #include <time.h> using namespace std; #define maxn 511111 vector <int> fac[maxn]; int prime[maxn], cnt; bool vis[maxn]; int mu[maxn]; int num[maxn]; //每个数能够分解成几个质数 long long sum[maxn][22]; long long n, m;int p; void init () { memset (vis, 0, sizeof vis); for (int i = 1; i <= 500000; i++) fac[i].clear (); cnt = 0; mu[1] = 1; for (int i = 2; i <= 500000; i++) { if (!vis[i]) { prime[cnt++] = i; mu[i] = -1; } for (int j = 0; j < cnt; j++) { if (i*prime[j] > 500000) break; vis[i*prime[j]] = 1; if (i%prime[j] == 0) { mu[i*prime[j]] = 0; break; } else { mu[i*prime[j]] = -mu[i]; } } } memset (vis, 0, sizeof vis); memset (num, 0, sizeof num); num[1] = 0; for (int i = 2; i <= 500000; i++) { if (!vis[i]) { for (int j = i; j <= 500000; j += i) { vis[j] = 1; int cur = j; while (cur%i == 0) { cur /= i; num[j]++; } } } } memset (sum, 0, sizeof sum); for (int i = 1; i <= 500000; i++) { for (int j = i; j <= 500000; j += i) { sum[j][num[i]] += mu[j/i]; } } for (int i = 1; i <= 500000; i++) { for (int j = 0; j <= 18; j++) { sum[i][j] += sum[i-1][j]; } } for (int i = 1; i <= 500000; i++) { for (int j = 1; j <= 18; j++) { sum[i][j] += sum[i][j-1]; } } } int main () { init (); int q; scanf ("%d", &q); while (q--) { scanf ("%lld%lld%d", &n, &m, &p); if (p >= 19) { printf ("%lld\n", n*m); continue; } p = min (18, p); long long ans = 0; int j; for (int i = 1; i <= min (n, m); i = j+1) { j = min (n/(n/i), m/(m/i)); ans += (long long)(sum[j][p]-sum[i-1][p])*(long long)(n/i)*(long long)(m/i); } printf ("%lld\n", ans); } return 0; }