第一次接触莫比乌斯反演,总之脑子都快要炸掉了……好难啊!本蒟蒻表示根本无法理解呜呜呜呜呜……不过在机房DL的帮助下总算是磕磕绊绊的A掉了这一题:
这道题目要我们的求的是:(1)ΣiΣj [gcd(i,j)==k], 区间范围内的限定我们都可以利用容斥来解决,所以默认为所求函数值的(i, j)取值范围为(1~n,1~m)。注意到gcd(i, j) == k比较的不好求解,我们把k除到外面去,得到:(2)ΣiΣj [gcd(i,j)==1](i∈n/k, j∈m/k); 这里将[gcd(i,j)==1]替换为单位元ε(ε(i) = i == 1 ? 1 : 0)。根据狄利克雷卷积有μ * 1 = ε(*为卷积符号),所以我们得到式子(3)ΣiΣjΣd|gcd(i,j)μ(d)。这里是考虑对于每一对i,j有哪几个d与之对应,我们将d提取出来,反之枚举符合d|gcd(i,j)的数对。由此有(4)Σd μ(d) * (n / (d*k)) * (m / (d * k))(d∈min(n, m) / (d * k))。
但是利用最后这个式子求解的复杂度依然太高了,不过因为n/i的取值最多只有2*sqrt(n)种(若i<sqrt(n),i的取值只有sqrt(n)种,若i>=sqrt(n),n/i<=sqrt(n),所以一共最多只有2*sqrt(n)种),所以在很大的范围内实际上(n / (d*k)) * (m / (d * k))都是一个常数,我们就可以利用μ(d)的前缀和来实现快速求解。
设当前枚举到的d,pos = min(n / (n / d), m / (m / d)); 这个pos就是从当前位置一直到达pos,(n / (d*k)) * (m / (d * k))都是一个常数,只需要计算一次之后利用前缀和求解。
贴代码,再一次被自己蠢哭,智商堪忧怎么办啊QAQ(向数学组DL低头,伏地%%%,跪地%%%!!!)
#include <bits/stdc++.h> using namespace std; #define maxn 65000 int tot, T, maxx = 60000, sum[maxn], pri[maxn], Mu[maxn]; bool is_prime[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } int Get_Mu() { Mu[1] = 1; for(int i = 2; i <= maxx; i ++) { if(!is_prime[i]) pri[++ tot] = i, Mu[i] = -1; for(int j = 1; j <= tot; j ++) { if(i * pri[j] > maxx) break; is_prime[i * pri[j]] = true; if(!(i % pri[j])) { Mu[i * pri[j]] = 0; break; } else Mu[i * pri[j]] = - Mu[i]; } } for(int i = 1; i <= maxx; i ++) sum[i] = sum[i - 1] + Mu[i]; } int cal(int n, int m) { if(n > m) swap(n, m); int ans = 0, pos; for(int i = 1; i <= n; i = pos + 1) { pos = min(n / (n / i), m / (m / i)); ans += (sum[pos] - sum[i - 1]) * (n / i) * (m / i); } return ans; } int main() { Get_Mu(); T = read(); while(T --) { int a = read(), b = read(), c = read(), d = read(), k = read(); a --, c --; a /= k, b /= k, c /= k, d /= k; int ans = cal(a, c) + cal(b, d) - cal(a, d) - cal(b, c); printf("%d ", ans); } return 0; }