- 小丑数(自己取的名字,为了跟前面的丑数和超级丑数区分开):可被a或b或c整除的正整数
- 因子只要包含a或b或c就认为是小丑数
- 丑数/超级丑数:只能包含
- 思路:
- 题目数据范围:
1 <= n, a, b, c <= 10^9
1 <= a * b * c <= 10^18
- 本题结果在
[1, 2 * 10^9]
的范围内 - 所以若用多路归并,
O
(
m
a
x
(
m
l
o
g
m
,
n
l
o
g
m
)
)
=
O
(
n
)
O(max(mlogm,nlogm))=O(n)
O(max(mlogm,nlogm))=O(n)必然会超时
- 需要 O ( l o g n ) O(logn) O(logn)算法
- 想到二分
- 题目要求第
n
个小丑数,设这个数为num
,那么可以这么看:[1,...,num]
范围内的小丑数有n
个
- 随着
num
增大,[1,...num]
范围内的小丑数增多- 随着
num
减小,[1,...num]
范围内的小丑数减少 - 符合单调性->可用二分
- 随着
- 所以设
x
为,f(x)
为[1,...,x]
范围内的小丑数个数,那么f(x)
是单调不递减函数- 找第
n
个小丑数即是找f(x)==n
的左边界 - 如何二分解决了
- 找第
- 题目要求第
- 接下来,如何找
[1,...,x]
范围内小丑数的个数?- 能被
a
整除的数有x/a
个,能被b
整除的数有x/b
个,能被c
整除的数有x/c
个- 但是不能直接加和,比如有的数既能被
a
整除又能被b
整除,直接加和会重复计算- 需要去掉重复的部分
- 容斥原理
- 需要去掉重复的部分
- 但是不能直接加和,比如有的数既能被
- 容斥原理:集合A、B、C内数的个数=
∣
A
+
B
+
C
∣
−
∣
A
∩
B
∣
−
∣
A
∩
C
∣
−
∣
B
∩
C
∣
+
∣
A
∩
B
∩
C
∣
|A + B + C| - |A ∩ B| - |A ∩ C| - |B ∩ C| + |A ∩ B ∩ C|
∣A+B+C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣
- 所以,
[1,...,x]
范围内小丑数(因子包含a或b或c的数)的个数- =能被
a
整除的数的个数+能被b
整除的数的个数+能被c
整除的数的个数-能同时被a、b
整除的数的个数-能同时被a、c
整除的数的个数-能同时被b、c
整除的数的个数+能同时被a、b、c
整除的数的个数 - 能同时被
a、b
整除的数的个数,即x/lcm(a, b)
,其中lcm(a, b)
是a和b的最小公倍数- 能同时被
a、b、c
整除的数的个数同理
- 能同时被
- =能被
- 所以,
- 能被
- 题目数据范围:
typedef long long ll;
int nthUglyNumber(int n, int a, int b, int c) {
int l = 0, r = (int)2e9;
int ans = 1;
while(l <= r){
int mid = l + ((r - l) >>1);
int k = f(mid, a, b, c);
if(k == n){
ans = mid;
r = mid - 1;
}
else if(k < n) l = mid + 1;
else r = mid - 1;
}
return ans;
}
int f(int x, int a, int b, int c){
ll setA = x/a, setB = x/b, setC = x/c; //long long防止溢出
ll setAB = x / lcm(a, b);
ll setAC = x / lcm(a, c);
ll setBC = x / lcm(b, c);
ll setABC = x / lcm(a, lcm(b, c));
return (setA + setB + setC - setAB - setAC - setBC + setABC); //容斥原理
}
ll lcm(ll a, ll b){
return (a * b / gcd(a, b));
}
ll gcd(ll a, ll b){
return b? gcd(b, a%b): a;
}