hdu 1695
题目:各处两个区间[1,b],[1,d]和k,取两区间中的元素x、y,使得gcd(x,y)=k;
问这种组合有多少种,gcd(y,x)=k和gcd(x,y)=k是相同的。
分析:数据量为100,000,呕心的k可以为0。
由数据可以知道不可以暴力
(1)由于gcd(x,y)=k 满足 gcd(x/k,y/k)=1(!!)
所以区间可以缩小为[1/k,b/k],[1/k,d/k]
问题也转换成为 取两区间中的元素x、y,使得gcd(x,y)=1;
(2)由于gcd(y,x)=1和gcd(x,y)=1是相同的,所以就有了 x<y,
于是可以先取小区间进行讨论,由于 gcd(x,y)=1 对于数论来说是
非常特殊的东西,所以就想到欧拉函数(phi[])(!!),所以小
区间可以计算为:
ans += phi[1]+phi[2]+...+phi[b]
(3)对于[b/k+1,d/k]的讨论,设y为区间的一个元素,
则可以对y进行质因数分解(不用保留指数)
于是得到集合: {p1, p2, p3 ,...} p为素数(!!)
虽然是要求 gcd=1 的组合,但反过来也可以求 gcd!=1的组合,
其实都一样,看个人习惯。于是可以用容斥原理进行统计能被这
些素数整除的数的个数,记得不可以一个一个来(有重复情况)(!!)
最后减一下求补数就可以加到ans
ps:可以会有疑问为什么要分解再统计,我没试过暴力,我想应该会
因为求gcd时用的时间会很多,然后会造成tle。
看另外那个区间有多少个数与i不互质。
容斥原理的具体如下:
区间中与i不互质的个数 = (区间中i的每个质因数的倍数个数)-(区间中i的每两个质因数乘积的倍数)+(区间中i的每3个质因数的成绩的倍数个数)-(区间中i的每4个质因数的乘积)+...
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define INT long long using namespace std; const int maxn = 100010; const int Mr = 400; INT phi[maxn]; bool notp[Mr]; int pr[200], pn; int a, b, c, d, k; void getprime() {//线性筛素数 pn = 0; memset(notp, 0, sizeof (notp)); int i, j; for (i = 2; i < Mr; i++) { if (!notp[i])pr[pn++] = i; for (j = 0; j < pn && pr[j] * i < Mr; j++) { notp[pr[j] * i] = 1; if (i % pr[j] == 0) break; } } } void init() { INT i, j; for (i = 1; i <= maxn; i++)phi[i] = i; //线性筛欧拉函数 for (i = 2; i <= maxn; i += 2) phi[i] /= 2; for (i = 3; i <= maxn; i += 2) { if (phi[i] == i) { for (j = i; j <= maxn; j += i) phi[j] = phi[j] / i * (i - 1); } } getprime(); } int p[100]; int ppn; int gcd(int x, int y) { if (!x || !y) return max(x, y); for (int t; t = x % y; x = y, y = t); return y; } int lcm(int a, int b) { return a / gcd(a, b) * b; } void fun(int k, int &ans) { ans = 1; int i; int cnt = 0; for (i = 0; i < ppn && k > 0; k >>= 1, i++) { if (k & 1) { ans = lcm(ans, p[i]); cnt++; } } if (!(cnt & 1)) ans = -ans; //偶数取反在这里取了!! } int calc(int n) { int i, j; ppn = 0; //分解质因数 for (i = 0; i < pn && pr[i] * pr[i] <= n; i++) { if (n % pr[i] == 0) { p[ppn++] = pr[i]; while (n % pr[i] == 0) n /= pr[i]; } } if (n != 1) p[ppn++] = n; int sum = 0; int tmp; for (i = 1; i < (1 << ppn); i++) { fun(i, tmp); //按二进制计算指定素数的最小公倍数 tmp = b / tmp; //计算可以被这个数整除的数字个数 sum += tmp; } return b - sum; //取反 } int main() { //freopen("in","r",stdin); init(); int T, tt; cin >> T; for (tt = 1; tt <= T; tt++) { scanf("%d%d%d%d%d", &a, &b, &c, &d, &k); if (b > d) swap(b, d); if (k == 0) {//这个k=0比较无聊,但以后要注意同类型的问题 printf("Case %d: 0\n", tt); continue; } b /= k; d /= k; INT ans = 0; int i, j; for (i = 1; i <= b; i++) { ans += phi[i]; } for (i = b + 1; i <= d; i++) {//对每个数字计算 ans += calc(i); } printf("Case %d: %I64d\n", tt, ans); } return 0; }