bzoj2301 [HAOI2011]Problem b【莫比乌斯反演 分块】

传送门: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\n", slove(b, d) - slove(a - 1, d) - slove(b, c - 1) + slove(a - 1, c - 1));
    }
    return 0;
}

 

  

 

posted @ 2017-02-06 22:17  ciao_sora  阅读(...)  评论(...编辑  收藏