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;
}