POJ 2773 - Happy 2006(暴力+思维 / 二分+容斥)
题目链接 https://vjudge.net/problem/POJ-2773
【题意】
给定两个整数m和k,找到第k个与m互素的数(互素的数从小到大排列),其中(1 <= m <= 1000000,1 <= k <= 100000000 )
【思路】
有两种思路,先说一种比较暴力的思路,比较好理解。有一个重要的结论要知道是gcd(a+b*t,b)=gcd(a,b),也就是说如果a+b*t与b互素,那么a与b也互素,反之也成立。现在要找的是与m互素的数,假设我们已经求出了[1,m-1]内与m互素的每一个数,假设第i个为a[i],那么根据gcd(a[i]+m*t,m)=gcd(a[i],m),t=1,2,3…所以我们就可以得到一些列的数a[i]+m*t,t=1,2,3…都是和m互素的,因此我们可以暴力求解出前面的每一个a[i],然后再增加若干倍的m就是结果了
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxm = 1000005;
int gcd(int a, int b) {
return 0 == b ? a : gcd(b, a%b);
}
int a[maxm];
int main() {
int m, k;
while (scanf("%d%d", &m, &k) == 2) {
int cnt = 0;
for (int i = 1; i <= m; ++i) {
if (gcd(m, i) == 1) {
a[cnt++] = i;
}
}
ll ans = a[(k - 1) % cnt];//下标从0开始,k-1向前偏移一位
ans += (ll)m*(ll)((k - 1) / cnt);
printf("%lld\n", ans);
}
return 0;
}
另外一种复杂度较低的算法思路是利用二分法,二分求解[1,x]中所有与m互素的数的个数,如果答案刚好为k,那么x就是第k个与m互素的数了。关键在于如何求解[1,x]中所有与m互素的数的个数,首先要对m进行唯一分解,拆分出m的每个素因子,然后根据容斥原理,用二进制法枚举每种素因子选择或不选择的状态,计算相应结果累加求和,举个例子假如要算[1,30]中所有与30互素的数,我们知道30=2*3*5,所以[1,30]中2的倍数有30/2=15个,3的倍数有30/3=10个,5的倍数有30/5=6个,既是2也是3的倍数即6的倍数有30/6=5个,既是2又是5的倍数即10的倍数有30/10=3个,既是3又是5的倍数即15的倍数有30/15=2个,既是2又是3还是5的倍数的数有1个就是30,所以由容斥原理,[1,30]中,与m不互素(即和m中有相同的素因子,也就是满足2或3或5的倍数)的个数=(15+10+6)-(5+3+2)+1=21个,那么再用30-22=8就是[1,30]中与m互素的数的个数。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll inf = 1e18;
ll m, k;
ll fac[10050], num;//素因数,素因数的个数
void init() {//唯一分解定理
num = 0;
ll cpy = m;
int n = (int)sqrt(m + 0.5);
for (int i = 2; i <= n; ++i) {
if (cpy % i == 0) {
fac[num++] = i;
while (cpy % i == 0) cpy /= i;
}
}
if (cpy > 1) fac[num++] = cpy;
}
ll check(ll x) {//返回[1,x]中与m互素的数的个数
if (x == 1) return 1;
if (m == 1) return x;
ll ans = 0;//不互素的个数
for (ll s = 1; s < (1 << num); ++s) {//二进制法枚举所有素因子的选择情况(注意从1开始枚举)
int cnt = 0;
ll f = 1;
for (int j = 0; j < num; ++j) {
if (s & (1 << j)) {
++cnt;
f *= fac[j];
}
}
//容斥原理统计,奇数加,偶数减
if (cnt & 1) ans += x / f;
else ans -= x / f;
}
return x - ans;
}
int main() {
while (scanf("%lld%lld", &m, &k) == 2) {
init();
ll le = 0, ri = inf, mid;
while (le + 1 < ri) {
mid = (le + ri) >> 1;
if (check(mid) < k) le = mid;
else ri = mid;
}
printf("%lld\n", ri);
}
return 0;
}