整理的算法模板合集: ACM模板
目录
题目 | 算法 |
---|---|
AcWing 1291. 轻拍牛头 | 求约数的个数 |
AcWing 1294. 樱花 | 求 ( n ! ) 2 (n!)^2 (n!)2的约数的个数 |
AcWing 198. 反素数 | 通过分析元素性质发现可以直接dfs |
一、约数个数
本节前半部分需要用到的知识:
由算数基本定理得正整数N可以写作 N = p 1 C 1 × p 2 C 2 × p 3 C 3 ⋯ × p m C m N=p_1^{C_1}\times p_2^{C_2} \times p_3^{C_3} \cdots \times p_m^{C_m} N=p1C1×p2C2×p3C3⋯×pmCm
N的正约数个数为( Π Π Π是连乘积的符号,类似 ∑ ∑ ∑)
(
c
1
+
1
)
×
(
c
2
+
1
)
×
⋯
(
c
m
+
1
)
=
Π
i
=
1
m
(
c
i
+
1
)
(c_1+1)\times (c_2+1)\times \cdots (c_m+1)=\Pi_{i=1}^{m}(ci+1)
(c1+1)×(c2+1)×⋯(cm+1)=Πi=1m(ci+1)
N的所有正约数和为
( 1 + p 1 + p 1 2 + ⋯ + p 1 c 1 ) × ⋯ × ( 1 + p m + p m 2 + ⋯ + p m c m ) = ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) (1+p_1+p_1^2+\cdots +p_1^{c_1})\times\cdots\times(1+p_m+p_m^2+\cdots +p_m^{c_m})=\prod_{i=1}^{m}(\sum_{j=0}^{c_i}(p_i)^j) (1+p1+p12+⋯+p1c1)×⋯×(1+pm+pm2+⋯+pmcm)=i=1∏m(j=0∑ci(pi)j)
1. AcWing 1291. 轻拍牛头
因为
如果我们分别求每个数的约数的个数,那么每次的时间复杂度至少是
O
(
n
)
O(\sqrt n)
O(n)的,所以我们没必要每次都一个一个求,我们正着直接求很难,那么我们就倒着求,我们类比线性筛的思路,如果对于一个数x而言,数d是x的约数,那么x一定是d的倍数,所以我们直接筛一遍倍数即可。时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
注意最后需要除去自己
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1000007;
int n, m;
int a[N];
int primes[N];
int cnt[N];
int S[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
cnt[a[i]] ++ ;
}
for(int i = 1; i <= 1e6; ++ i){//nlogn
for(int j = i; j <= 1e6; j += i){
S[j] += cnt[i];
}
}
for(int i = 1; i <= n; ++ i)
printf("%d\n", S[a[i]] - 1);//除去它自己
return 0;
}
2. AcWing 1294. 樱花
所以本题实际上转换成了求
n
!
2
n!^2
n!2的约数的个数,我们根据公式,
由算数基本定理得正整数N可以写作 N = p 1 C 1 × p 2 C 2 × p 3 C 3 ⋯ × p m C m N=p_1^{C_1}\times p_2^{C_2} \times p_3^{C_3} \cdots \times p_m^{C_m} N=p1C1×p2C2×p3C3⋯×pmCm
N的正约数个数为( Π Π Π是连乘积的符号,类似 ∑ ∑ ∑)
( c 1 + 1 ) × ( c 2 + 1 ) × ⋯ ( c m + 1 ) = Π i = 1 m ( c i + 1 ) (c_1+1)\times (c_2+1)\times \cdots (c_m+1)=\Pi_{i=1}^{m}(ci+1) (c1+1)×(c2+1)×⋯(cm+1)=Πi=1m(ci+1)
求一个数的约数个数是分解质因数连乘,那么求一个数的阶乘的约数的个数实际上就是求1~n的每个数的约数个数的乘积。
因为 n ! 2 n!^2 n!2太大了,所以我们引入一道题目:我们发现只需要将阶乘分解之后,所有质因子的指数的乘积的二倍既是答案。
2.1 AcWing 197. 阶乘分解
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
int primes[N], cnt;
bool st[N];
void init(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] * i <= n; j ++ )
{
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
int main()
{
int n;
cin >> n;
init(n);
for (int i = 0; i < cnt; i ++ )
{
int p = primes[i];
int s = 0;
for (int j = n; j; j /= p) s += j / p;
printf("%d %d\n", p, s);
}
return 0;
}
所以我们只需要在上述代码中稍作修改,累积的是每个质因子的指数的二倍即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1000007;
typedef long long ll;
ll mod = 1e9 + 7;
ll n, m, cnt;
int primes[N];
bool vis[N];
void get_primes(ll n)
{
vis[0] = vis[1] = 1;
for(int i = 2; i <= n; ++ i){
if(vis[i] == 0){
primes[++ cnt] = i;
}
for(int j = 1; j <= cnt && i * primes[j] <= n; ++ j){
vis[i * primes[j]] = true;
if(i % primes[j] == 0)break;
}
}
}
int main(){
scanf("%lld", &n);
get_primes(n);
ll res = 1;
for(int i = 1; i <= cnt; ++ i){
int p = primes[i];
int sum = 0;
for(int j = n; j; j /= p)
sum += j / p;
res = (res * (2 * sum + 1)) % mod;
}
cout << res % mod << endl;
return 0;
}
3. AcWing 198. 反素数
很重要的一个解决问题的方法:找题目的性质!!!
如果没有思路的话,或者说数论的题目,所给的条件太少了,我们可以找一下题目中各各变量的性质!
所以我们直接爆搜即可。然后搜的过程中应用那三个我们发现的性质,其中这三个性质在之后的题目中用处也是非常大的。
//我们只用到了最多9个质因子,因为再多乘起来就大于1e9了,所以我们直接打表找到前9个质因子即可。
//我们需要找到的是约数个数最多的最小的数
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 500007;
typedef long long ll;
int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int n, m;
int maxnum, number;
//使用的是第几个质数,最大次数,乘起来得到的x(反素数备选项),当前约数的个数(我们要找的是约数个数最多的最小的数)
void dfs(int cnt, int last_pow, int mul, int num)
{
if(num > maxnum || (num == maxnum && mul < number)){
maxnum = num;
number = mul;
}
if(cnt == 9)return ;
for(int i = 1; i <= last_pow; ++ i){//当前质因子的次数
if((ll)primes[cnt] * mul > n) break;
mul *= primes[cnt];//这个是要修改的
dfs(cnt + 1, i, mul, num * (i + 1));//约数个数就是Π(ci + 1)
}
}
int main()
{
scanf("%d", &n);
dfs(0, 30, 1, 1);
cout << number << endl;
return 0;
}
4. AcWing 200. Hankson的趣味题
我的标程(能过原题):
但是y总竟然造了一组数据把标程给卡T了…
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 50007;
int n, m, t, ans;
int a, b, c, d;
int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-')f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int gcd(int a, int b)
{
if(b == 0)return a;
return gcd(b, a % b);
}
int main()
{
t = read();
while(t -- ){
ans = 0;
a = read(), b = read(), c = read(), d = read();
for(int x = 1, y; x * x <= d; ++ x)
{
if(d % x == 0){
if(gcd(x, a) == b && d * gcd(x, c) == x * c){
ans ++ ;
}
if(x != d / x){
y = d / x;
if(gcd(y, a) == b && d * gcd(y, c) == y * c){
ans ++ ;
}
}
}
}
printf("%d\n", ans);
}
return 0;
}
%%%
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 45000, M = 50;
int primes[N], cnt;
bool st[N];
PII factor[M];
int cntf;
int divider[N], cntd;
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
void dfs(int u, int p)
{
if (u > cntf)
{
divider[cntd ++ ] = p;
return;
}
for (int i = 0; i <= factor[u].second; i ++ )
{
dfs(u + 1, p);
p *= factor[u].first;
}
}
int main()
{
get_primes(N);
int n;
scanf("%d", &n);
while (n -- )
{
int a0, a1, b0, b1;
scanf("%d%d%d%d", &a0, &a1, &b0, &b1);
int d = b1;
cntf = 0;
for (int i = 0; primes[i] <= d / primes[i]; i ++ )
{
int p = primes[i];
if (d % p == 0)
{
int s = 0;
while (d % p == 0) s ++, d /= p;
factor[ ++ cntf] = {p, s};
}
}
if (d > 1) factor[ ++ cntf] = {d, 1};
cntd = 0;
dfs(1, 1);
int res = 0;
for (int i = 0; i < cntd; i ++ )
{
int x = divider[i];
if (gcd(x, a0) == a1 && (LL)x * b0 / gcd(x, b0) == b1)
{
res ++ ;
}
}
printf("%d\n", res);
}
return 0;
}
二、欧拉函数
线性筛筛欧拉函数的原理
1. AcWing 201. 可见的点
用线性筛筛欧拉函数即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 500007, M = 1000007, INF = 0x3f3f3f3f;
const double inf = 1e100;
const double eps = 1e-8;
const int mod = 10007;
int primes[N], cnt;
int phi[N];
bool vis[N];
void init(int n)
{
phi[1] = 1;
for(int i = 2; i <= n; ++ i){
if(!vis[i]){
primes[ ++ cnt] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= cnt && i * primes[j] <= n; ++ j){
vis[i * primes[j]] = true;
if(i % primes[j] == 0){//最小的质因子
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
else phi[i *primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
int n, m, t, kcase;
int main()
{
scanf("%d", &t);
init(N - 1);
while(t -- ){
scanf("%d", &n);
ll ans = 0;
for(int i = 1; i <= n; ++ i)
ans += phi[i] * 2;
printf("%d %d %lld\n", ++ kcase, n, ans + 1);
}
return 0;
}
2. AcWing 220. 最大公约数
我们可以莫比乌斯反演!
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 10000007;
int n, m;
int primes[N];
int phi[N];
int cnt;
bool vis[N];
ll sum[N];
void init(int n)
{
phi[1] = 0;//这里应该是0
for(int i = 2; i <= n; ++ i){
if(vis[i] == 0){
primes[ ++ cnt] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= cnt && primes[j] * i <= n; ++ j){
vis[i * primes[j]] = true;
if(i % primes[j] == 0){
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
for(int i = 1; i <= n; ++ i)//求phi[n]的前缀和
sum[i] = sum[i - 1] + phi[i];
}
int main()
{
scanf("%d", &n);
init(n);
ll ans = 0;
for(int i = 1; i <= cnt; ++ i){
int p = primes[i];
ans += sum[n / p] * 2 + 1;
}
printf("%lld\n", ans);
return 0;
}