传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2301
很好的一道题。首先把每个询问转化为4个子询问,最后的结果就是这四个子询问的记过加加减减,类似二维前缀和。那么问题转化为在1 <= x <= lmtx, 1 <= y <= lmty时gcd(x, y) == k的对数,这个问题在转化一下,转化成1 <= x <= lmtx / k,1 <= y <= lmty / k时x与y互质的对数。莫比乌斯反演一下,就有了,但是会TLE,所以需要分块优化。
其它博客讲得很清楚了,程序精华在15~16行。
#include <cstdio> #include <algorithm> const int maxn = 50005; int n, a, b, c, d, k, mu[maxn] = {0, 1}, prime[maxn], tot, s[maxn]; char book[maxn]; inline long long slove(int lmtx, int lmty) { lmtx /= k; lmty /= k; int lmt = std::min(lmtx, lmty), last; long long rt = 0; for (int i = 1; i <= lmt; i = last + 1) { last = std::min(lmtx / (lmtx / i), lmty / (lmty / i)); rt += (long long)(lmtx / i) * (lmty / i) * (s[last] - s[i - 1]); } return rt; } int main(void) { scanf("%d", &n); for (int i = 2; i < maxn; ++i) { if (!book[i]) { prime[tot++] = i; mu[i] = -1; } for (int j = 0; j < tot; ++j) { if (i * prime[j] > maxn) { break; } book[i * prime[j]] = 1; if (i % prime[j] == 0) { break; } else { mu[i * prime[j]] = -mu[i]; } } } for (int i = 1; i < maxn; ++i) { s[i] = s[i - 1] + mu[i]; } while (n--) { scanf("%d%d%d%d%d", &a, &b, &c, &d, &k); printf("%lld ", slove(b, d) - slove(a - 1, d) - slove(b, c - 1) + slove(a - 1, c - 1)); } return 0; }