题目
给定两个正整数 n 和 m,以及一个长为 m 的序列 a。
请计算出最大的 k,使得能在序列中选出 k 个数 b1,b2,...,bk,满足 b1!×b2!×⋯×bk! 是 n! 的因数。
对于 100% 的数据,1≤m,ai≤2×105,1≤n≤109。
题解
首先考虑暴力,发现会出现很多问题,包括超long long等问题,所以考虑正解
观察到一个性质,选小的集合一定比选大的集合更优,所以先把输入的a排个序,然后就可以二分k,check一下前k个是否是n的因数
然后我考场上就卡在这里了
考虑一下,如何判断一个大数是否是另一个大数的因数呢——分解质因数
不过对于这么大的数,分解并保存检验他的质因数显然不太合理,于是我们考虑先把所有的质数求出来再来计算——埃氏筛即可
对于我们筛出来的每一个质数,在n!和 $b_1 ! \times b_2 ! \times \cdots \times b_k ! $ 的质因数分解后的集合中分别统计其出现了多少次,如前者小于后者则不可行。转化成下面两个问题
1.如何统计一个质数在n!的质因数分解集合中出现了几次
n!中可能会出现p的倍数,p2的倍数,p3的倍数,如何计算呢?
n!=1∗p∗⋯∗2∗p∗⋯∗3∗p∗⋯∗(n/p)∗p
然后是p2,由于其的一部分已经在p的时候算过了,所以我们只需让n/=p再次计算,以此类推,最后所有的结果相加即可
2.如何统计一个质数在b1!×b2!×⋯×bk!中出现了几次
首先我们创立一个cnt
数组,使之记录上式中每一个数出现了几次>
然后就和上面同理,看p的倍数,p2的倍数,p3的倍数。
另,注意到筛质数只需要处理到最大的a即可
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| #include<bits/stdc++.h>
using namespace std; #define maxn 200000 #define ll long long const int N = 2e5 + 10; int a[N], p[N], vis[N], cnt[N], tot; int n, m;
int ask(int p, int n) { if(n == 0) return 0; return n / p + ask(p, n / p); }
bool check(int x) { memset(cnt, 0, sizeof cnt); for(int i = 1;i <= x;i++) cnt[a[i]]++; for(int i = N - 2;i >= 0;i--) { cnt[i] += cnt[i + 1]; }
for(int i = 1;i <= tot;i++) { int c = ask(p[i], n); long long b = 0; for(int j = p[i]; j < N / p[i];j *= p[i]) { for(int k = j;k < N;k += j) { b += cnt[k]; } }
if(b > c) return 0; } return 1; }
int main() { freopen("a.in", "r", stdin); freopen("a.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(0); cin >> n >> m; for(int i = 1;i <= m;i++) { cin >> a[i]; } sort(a + 1, a + 1 + m); vis[0] = vis[1] = 1; for(int i = 2;i < N;i++) { if(vis[i]) { continue; } p[++tot] = i; for(int k = 2 * i;k < N;k += i) { vis[k] = 1; } } int l = 0, r = m;
while(l < r) { int mid = (l + r + 1) >> 1; if(check(mid)) l = mid; else r = mid - 1; } printf("%d", l); }
|