HDU-4135-Co-prime
这道题是容斥~
题目大意:给你一个区间[a, b],和一个数n,让你求区间[a, b]范围内的与n互质的数有多少个。
解题思路:我们看看数据范围,a和b的区间范围达到了1e15,n的数据范围达到了1e9。t次询问范围在100以内。
我们把这道题的思路与前缀和结合起来想一想。求区间[a, b]与n互质的数字,那我们转化成求[1, b]区间与n互质的个数sum1,求[1, a - 1]区间与n互质的个数sum2。然后sum1 - sum2就是我们的区间[a, b]与n互质的个数了。
再把问题转化一下:我们求[1, x]区间与n不是互质的个数(因为求互质的话,是不是想到了欧拉函数,但是欧拉函数在这道题也不行)
不是互质的(gcd(k, n) != 1)
我们最普遍的方法就是找到n的质因数,求范围在[1, x]之间的质因数的倍数,这些倍数肯定是和n不是互质的。算出这些个数cnt之后。我们就可以用总数减去cnt,就可以得到我们的互质的个数啦~
举个例子:
当x = 20,n = 10;
n的质因数:2, 5;(任何数都可以用质因数的乘积表示,及质因数分解)
接下来我们算在区间[1, 20]范围内的质因数的倍数:
2的倍数:2,4,6,8,10,12,14,16,18,20;(10个即:20 / 2)
5的倍数:5,10,15,20;(4个即:20 / 5)
你想直接用10 + 4 = 14得到不是互质的个数吗,不,你错了,我们看上面很明显有重复的。
所以我们需要用到容斥~
我们把上面的倍数相应的减掉之后,需要加上重复减掉的数字,比如10,20;
这些数是2和5的乘积的倍数,即10的倍数。
如果设计到多个质因数,我们还可能遇到加多了的情况,所以我们要相应的减去。
即得到规律:
奇数个质因数的个数是要减去的,偶数个质因数的个数是要加上的
归结为:
在区间[1. x]中找到与n互质的数的个数的公式:(这里假设n的质因数有3个)
个数 = x - x / n的质因数1 - x / n的质因数2 - x / n的质因数3 + x / (n的质因数1n的质因数2)+ x / (n的质因数2n的质因数3)+ x / (n的质因数1n的质因数3)- x / (n的质因数1n的质因数2 * n的质因数3)
更多的质因数以此类推。
然后我们考虑一下求质因数的环节:
暴力肯定是不行了,预处理也不行。因为这个范围1e9,空间限制。
所以我们只有采用输入一个数n挨个挨个算出,这个数的质因数。这样不会tle。
这里求质因数直接用的埃筛哈~
cal(x, s)函数算的就是在区间[1, x]上与s互质的个数有多少。
好啦,我说完啦~
代码部分:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 10;
ll a, b, n;
ll ans;
int cnt;
vector<ll> p;
ll q[N];
void init(ll x)
{
p.clear();
for (ll j = 2; j * j <= x; j++)
{
if (!(x % j))
{
p.push_back(j);
while (!(x % j))
{
x /= j;
}
}
}
if (x > 1)
{
p.push_back(x);
}
}
ll cal(ll x, ll s)
{
int num = 0;
q[num++] = 1;
int siz = p.size();
for (int i = 0; i < siz; i++)
{
ll t = p[i];
if (t > x)
{
break;
}
int k = num;
for (int j = 0; j < k; j++)
{
q[num++] = q[j] * t * (-1);
}
}
ll sum = 0;
for (int i = 0; i < num; i++)
{
sum += x / q[i];
}
return sum;
}
int main()
{
int t;
scanf ("%d", &t);
while (t--)
{
scanf ("%I64d%I64d%I64d", &a, &b, &n);
init(n);
ans = cal(b, n) - cal(a - 1, n);
cout << "Case #" << ++cnt << ": ";
cout << ans << endl;
}
return 0;
}