数论 筛法思想1
1.求解[1,n][1,n][1,n] 内每个数的约数的个数的和,n=1e6n = 1e6n=1e6
例如:
n=4n=4n=4
d(1)=1,d(2)=2,d(3)=2,d(4)=3,1+2+2+3=8d(1)=1,d(2)=2,d(3)=2,d(4)=3 , 1+2+2+3=8d(1)=1,d(2)=2,d(3)=2,d(4)=3,1+2+2+3=8
思路:
定义valival_ivali为iii的约数个数
那么我们只需要对valvalval数组求前缀和就是[1,n][1,n][1,n]内每个数的约数的个数的和
求约数个数
定义一个iii循环1−>n1->n1−>n
每次都用jjj从往后面筛,jjj每次加iii
那么每次jjj一定是iii的倍数
所以val[j]++val[j]+ +val[j]++即可
再求和dp[1,n]dp[1,n]dp[1,n]就是每个数的约数的个数和
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6 + 10;
int val[MAX];
int n,S=0;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
val[j]++;
}
S += val[i];
}
cout << dp[n];
return 0;
}
优化:
之前我们是枚举每位数有多少个约数,然后求和就是答案
现在我们枚举约数出现的次数
n=5时1出现了4次2出现了2次3出现了1次4出现了1次5出现了1次
n=5时\\
1出现了4次\\
2出现了2次\\
3出现了1次\\
4出现了1次\\
5出现了1次
n=5时1出现了4次2出现了2次3出现了1次4出现了1次5出现了1次
不难发现 [1,n][1,n][1,n]之间每个约数的出现次数为ni\frac{n}{i}in
cont=∑i=1n⌊ni⌋
cont=\sum_{i=1}^n\lfloor \frac{n}{i} \rfloor
cont=i=1∑n⌊in⌋
所以我们代码可以改成
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, val = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
val += n / i;
}
cout << val;
return 0;
}
这样的话复杂度为O(n)O(n)O(n)
但如果n<=1010n<=10^{10}n<=1010的话显然O(n)O(n)O(n)的写法会超时
优化
每次输出n/in/in/i的值,不难发现n/in/in/i的值都是成块出现的
30
30 15 10 7 6 5 4 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
111
通过输出我们可以看出做了非常多重复的计算
我们只需要枚举值等于当前块的iii的最小值减去上一个块的右端点再乘它的次数就是答案
如果能简化成
val = 1 * 30 + 1 * 15 + 1 * 10 + 1 * 7 + 1 * 6 + 1 * 5 + 1 * 4 + 3 * 3 + 5 * 2 + 15 * 1
那么外循环的次数就是块数,内循环使用二分寻找当前快的右端点
那么复杂度就能降到O(n∗logn)O(\sqrt n * logn)O(n∗logn)
这样就能处理1e101e101e10的数据
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main() {
ll n;
cin >> n;
ll val = 0;
ll R = 0;//记录上一个块的右端点
//右端点大于n就退出循环
for (int i = 1; R + 1 <= n; i++) {
ll l = R + 1, r = n;
while (l <= r) {
ll mid = (l + r) >> 1;
if (n / mid >= n / (R+1)) l = mid + 1;
else r = mid - 1;
}
val += (r - R) * (n / r);//块长度(值的个数) * 值
R = r;//更新右端点
}
cout << val;
return 0;
}
2 求解[1,n][1,n][1,n] 内有多少对数i,ji,ji,j 满足 iii 是 jjj的约数,n=1e6n = 1e6n=1e6
多少对约数,枚举每一个约数有多少数字符合
n=5,ans=5n = 5 , ans = 5n=5,ans=5
n=6,ans=8n=6,ans=8n=6,ans=8
暴力
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6 + 10;
int val;
int n,S=0;
int main() {
cin >> n;
//外循环枚举 [1,n]
for (int i = 1; i <= n; i++) {
//从 i+i 开始枚举 i 的倍数
for(int j=i+i;j<=n;j+=i){
val++;
}
}
cout << val;;
return 0;
}
不难发现,这个与上面那个题相比每次少加了一次
公式为
cont=∑i=1n(⌊ni⌋−1)=∑i=1n⌊ni⌋−n
cont=\sum_{i=1}^n(\lfloor \frac{n}{i} \rfloor-1)\\
=\sum_{i=1}^n\lfloor \frac{n}{i} \rfloor-n
cont=i=1∑n(⌊in⌋−1)=i=1∑n⌊in⌋−n
所以可以直接化简为
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main() {
ll n;
cin >> n;
ll val = 0;
ll R = 0;//记录上一个块的右端点
//右端点大于n就退出循环
for (int i = 1; R + 1 <= n; i++) {
ll l = R + 1, r = n;
while (l <= r) {
ll mid = (l + r) >> 1;
if (n / mid >= n / (R+1)) l = mid + 1;
else r = mid - 1;
}
val += (r - R) * (n / r);//块长度(值的个数) * 值
R = r;//更新右端点
}
cout << val - n;//输出减 n 就是对数
return 0;
}
3 给定一个序列aia_iai , 有多少对数ai,aja_i,a_jai,aj 成倍数。输出对数
序列长度n=2e5n = 2e5n=2e5,ai≤2e5a_i \leq 2e5ai≤2e5 ,保证序列中每个数两两不同
例如:
n=4,a=[1,3,6,2]n = 4 , a = [1,3,6,2]n=4,a=[1,3,6,2]
输出:555
思路:
成倍数就是有多少对ai,aja_i,a_jai,aj,aia_iai是aja_jaj的约数
预处理每一个数的约数数组
d[i]d[i]d[i]为iii的所有约数
定义cnt[i]cnt[i]cnt[i]为iii出现的次数,因为题目保证两两不同,所以我们遇到arr[i]arr[i]arr[i]让cnt[arr[i]]=1cnt[arr[i]]=1cnt[arr[i]]=1即可
最后遍历数组,加上每一个数的约数出现的次数就是答案
#include <bits/stdc++.h>
#define ll long long
const int MAX = 1e5 + 10;
using namespace std;
vector<int> d[MAX];
int arr[MAX];
int cnt[MAX];
int n, val;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
cnt[arr[i]] = 1;
}
//预处理每个数的约数数组
for (int i = 1; i < MAX; i++) {
for (int j = i + i; j < MAX; j += i) {
d[j].push_back(i);
}
}
//遍历每个数的约数 加上约数出现的次数
for (int i = 1; i <= n; i++) {
for (int j = 0; j < d[arr[i]].size(); j++) {
val += cnt[d[arr[i]][j]];
}
}
cout << val;
return 0;
}
二:
定义cnt[i]cnt[i]cnt[i]为iii是否出现
循环枚举i−>[1,MAX]i->[1,MAX]i−>[1,MAX],jjj从i+ii+ii+i开始枚举到MAX,这样jjj一定是iii的倍数
valvalval直接加cnt[j]cnt[j]cnt[j]就行
#include <bits/stdc++.h>
#define ll long long
const ll MAX = 2e5;
using namespace std;
int cnt[MAX+10], arr[MAX+10];
int n, val;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
cnt[arr[i]] = 1;
}
for (int i = 1; i <= MAX; i++) {
if(cnt[i]){
for (int j = i + i; j <= MAX; j += i) {
val += cnt[j];
}
}
}
cout << val;
return 0;
}
4 输出[1,n][1,n][1,n]每个数质因分解后的最小质因子,最大质因子以及不同质因子的个数 , n<=1e6n <= 1e6n<=1e6
筛法
定义 miimi_imii为iii的最小质因子 mximx_imxi为iii的最大质因子 cnticnt_icnti为iii的不同质因子个数
#include <bits/stdc++.h>
const int MAX = 1e6;
using namespace std;
int mi[MAX + 10], mx[MAX + 10], cnt[MAX + 10];
int n;
int main() {
cin >> n;
for (int i = 2; i <= n; i++) {
if (mi[i] == 0) {//没有被筛过 当前 i 是质数
mi[i] = i;//质数的最小质因子就是自己
mx[i] = i;//质数的最大质因子也是自己
cnt[i] ++;//质因子个数为一个, 1 不是质数
//以当前质数为起点 开始筛 j 一定是 i 的倍数
for (int j = i + i; j <= n; j += i) {
mx[j] = i;//最大值每次都要覆盖 最后一次覆盖的值一定是最大值
cnt[j] ++;
if (mi[j] != 0) continue;//第一次筛的就是最小值 所以最小值填过了直接跳过
mi[j] = i;
}
}
}
return 0;
}
5 输出[1,n][1,n][1,n]输出每个数质因子的个数(相同的也算) , n=1e6n = 1e6n=1e6
例如:
16=24=416 = 2^4=416=24=4
60=22∗3∗5=460=2^2*3*5=460=22∗3∗5=4
定义dpidp_idpi为iii质因子的个数
不难发现 对于每个iii
dp[i]=dp[i/mi[i]]+1;
dp[i] = dp[i/mi[i]]+1;
dp[i]=dp[i/mi[i]]+1;
其中mi[i]mi[i]mi[i]是iii的最小质因子,我们只需要预处理mi[i]mi[i]mi[i]就能O(n)O(n)O(n)的处理
#include <bits/stdc++.h>
const int MAX = 1e6;
using namespace std;
int dp[MAX + 10];
int mi[MAX + 10];
int n;
int main() {
cin >> n;
for (int i = 2; i <= MAX; i++) {
if (mi[i] == 0) {
mi[i] = i;
for (int j = i + i; j <= MAX; j += i) {
if (mi[j] != 0) continue;
mi[j] = i;
}
}
}
for (int i = 2; i <= n; i++) {
dp[i] = dp[i / mi[i]] + 1;
}
return 0;
}
6 给出长度为n(1 <= n <= 1e51e51e5)的数列(1 <= ai <= 1e51e51e5),问最多能找出长度为多少的子序列,满足子序列中任意两个数不互素?
∀i,j,gcd(ai,aj)≠1 \forall i,j ,gcd(a_i,a_j) \neq 1 ∀i,j,gcd(ai,aj)=1
a=[2,6,4,5,7,8,1] \Huge a=[2,6,4,5,7,8,1] a=[2,6,4,5,7,8,1]
1.枚举数i,扫序列看有多少个数能够被i整除,然后取他们的最大值
#include <bits/stdc++.h>
const int MAX = 1e5;
using namespace std;
int arr[MAX + 10];
int prime[MAX + 10];
int n, Mx;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> arr[i];
for (int i = 2; i <= MAX; i++) {
if (!prime[i]) {
int val = 0;
for (int j = i + i; j <= MAX; j++) prime[j] = 1;
for (int j = 1; j <= n; j++) val += arr[j] % i == 0;
Mx = max(val, Mx);
}
}
cout << Mx;
return 0;
}
2.枚举每个数a_i,统计质因子出现的次数。出现最多的就是答案。
#include <bits/stdc++.h>
const int MAX = 1e5;
using namespace std;
int cnt_prime[MAX + 10];
int prime[MAX + 10];
vector<int> Pr;
int n, len, Mx;
int main() {
cin >> n;
// 预处理 [1,MAX] 之间的所有质数
for (int i = 2; i <= MAX; i++) {
if (!prime[i]) {
Pr.push_back(i);
for (int j = i + i; j <= MAX; j++) prime[j] = 1;
}
}
len = Pr.size();
for (int i = 1; i <= n; i++) {
int val;
cin >> val;
for (int j = 0; j < len; j++) {
if (val < Pr[j]) break;
if (!(val % Pr[j])) {
cnt_prime[Pr[j]]++;
while (!(val % Pr[j])) val /= Pr[j];
}
}
}
for (int i = 0; i < len; i++) {
Mx = max(Mx, cnt_prime[Pr[i]]);
}
cout << Mx;
return 0;
}