矩阵快速幂
通过构造一个
3
⋅
3
3·3
3⋅3的矩阵来快速求得大数斐波那契和。
定义
F
n
=
(
f
n
,
f
n
+
1
,
S
n
)
F_n=(f_n,f_{n+1},S_n)
Fn=(fn,fn+1,Sn)分别代表斐波那契数和前n项的和
构 造 A = [ 0 1 0 1 1 1 0 0 1 ] 构造A=\begin{bmatrix} 0 & 1&0 \\ 1 & 1&1 \\0&0&1 \end{bmatrix} 构造A=⎣⎡010110011⎦⎤
可使得 F n + 1 = F n ⋅ A F_{n+1}=F_n·A Fn+1=Fn⋅A,所以 F n = F 1 ⋅ A n − 1 F_n=F_1·A^{n-1} Fn=F1⋅An−1,A的次方可以用快速幂来实现。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 3;
int n, m;
void mul(int c[], int a[], int b[][N])//形成新的Fn矩阵
{
int temp[N] = {0};
for (int i = 0; i < N; i ++ )
for (int j = 0; j < N; j ++ )
temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;
memcpy(c, temp, sizeof temp);//矩阵做参数可以自动传回
}
void mul(int c[][N], int a[][N], int b[][N])//重塑a矩阵
{
int temp[N][N] = {0};
for (int i = 0; i < N; i ++ )
for (int j = 0; j < N; j ++ )
for (int k = 0; k < N; k ++ )
temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;
memcpy(c, temp, sizeof temp);
}
int main()
{
cin >> n >> m;
int f1[N] = {1, 1, 1};
int a[N][N] = {
{0, 1, 0},
{1, 1, 1},
{0, 0, 1}
};
n -- ;//计算A的n-1次方
while (n)
{
if (n & 1) mul(f1, f1, a); // res = res * a
mul(a, a, a); // a = a * a
n >>= 1;
}
cout << f1[2] << endl;
return 0;
}
阶乘分解
题意:给定整数
N
N
N,把阶乘
N
N
N 分解质因数,按照算术基本定理的形式输出分解结果中的 质因子 和 对应次数。
1
≤
N
≤
1
0
6
1≤N≤10^6
1≤N≤106.
思路: 这道题暴力的话是枚举1~N的每一个数,对每一个数分解质因数,然后求和,时间复杂度大概是
n
⋅
n
n·\sqrt{n}
n⋅n,显然超时。
首先
1
1
1——
n
n
n 中所有质数的个数大概是
n
l
o
g
n
\dfrac{n}{log_n}
lognn,这个题大概是
50000
50000
50000左右。由算数基本定理可知,在
1
1
1——
n
连
乘
n连乘
n连乘 中对于每一个质数
p
i
p_i
pi ,
p
p
p 的次数是
n
p
+
n
p
2
+
n
p
3
+
.
.
.
.
n
p
m
(
p
m
<
=
n
)
\dfrac{n}{p}+\dfrac{n}{p^2}+\dfrac{n}{p^3}+....\dfrac{n}{p^m}(p^m<=n)
pn+p2n+p3n+....pmn(pm<=n)。
因为对于任意的
C
C
C·
p
k
p^k
pk,k总能被最小因子的次方累加。比如
10
!
10!
10!中求2的次数。
10
2
1
\dfrac{10}{2^1}
2110
=
5
=5
=5是
2
1
2^1
21的贡献,
10
2
2
\dfrac{10}{2^2}
2210
=
2
=2
=2是
2
2
2^2
22 的贡献,
10
2
3
\dfrac{10}{2^3}
2310
=
1
=1
=1是
2
3
2^3
23的贡献,因此很容易求出
1
n
1~n
1 n中
2
2
2 的次数。而在这个过程是分母指数枚举到
n
n
n,所以求每一个次数的时间大概是
l
o
g
n
log_n
logn。
所以总的时间复杂度大概是
n
l
o
g
n
⋅
l
o
g
n
=
O
(
n
)
\dfrac{n}{log_n}·log_n=O(n)
lognn⋅logn=O(n)
#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;
}
AcWing 196. 质数距离(二次筛法)
给定两个整数 L L L 和 U U U,你需要在闭区间 [ L , U ] [L,U] [L,U] 内找到距离最接近的两个相邻质数 C 1 C1 C1 和 C 2 C2 C2(即 C 2 − C 1 C2−C1 C2−C1 是最小的),如果存在相同距离的其他相邻质数对,则输出第一对。
同时,你还需要找到距离最远的两个相邻质数 D 1 D1 D1 和 D 2 D2 D2(即 D 1 − D 2 D1−D2 D1−D2 是最大的),如果存在相同距离的其他相邻质数对,则输出第一对。
输入格式
每行输入两个整数
L
L
L 和
U
U
U,其中
L
L
L 和
U
U
U 的差值不会超过
1
0
6
10^6
106。
输出格式
对于每个 L 和 U,输出一个结果,结果占一行。
结果包括距离最近的相邻质数对和距离最远的相邻质数对。(具体格式参照样例)
如果 L 和 U 之间不存在质数对,则输出 There are no adjacent primes.。
数据范围
1
≤
L
<
U
≤
2
31
−
1
1≤L<U≤2^{31}−1
1≤L<U≤231−1
输入样例:
2 17
14 17
输出样例:
2,3 are closest, 7,11 are most distant.
There are no adjacent primes.
思路:这道题最大的N的范围大概是
2
e
9
2e9
2e9,如果直接筛复杂度起码是线性的,而且
1
到
2
e
9
1到2e9
1到2e9之前的质数大概是
n
l
o
g
=
1
e
8
\dfrac{n}{log}=1e8
logn=1e8左右,数组也开不了那么大。所以我们干脆不筛
2
e
9
2e9
2e9范围内所有的质数,而转化为只筛
n
\sqrt{n}
n范围内所有质数即可。
对
于
n
中
的
质
数
,
至
少
会
存
在
一
个
d
满
足
:
d
<
=
n
d
对于n中的质数,至少会存在一个 d满足: d<=\dfrac{n}{d}
对于n中的质数,至少会存在一个d满足:d<=dn即最大的第一个质因数也不超过
n
\sqrt{n}
n。所以我们可以只枚举前
n
\sqrt{n}
n范围的的质数
(
n
≈
50000
)
(\sqrt{n}≈50000)
(n≈50000),对于这个范围内的每一个质数,再枚举它的倍数,然后删掉在
L
到
U
L到U
L到U区间里的所有合数,这样对于L到U范围内剩下的数也是质数。(因为这个质数本身是合法的,所以筛其倍数的时候起码从两倍开始筛)
已知
L
L
L 和
U
U
U 的差值不会超过
1
0
6
10^6
106,所以对于任意的一个
p
p
p,它最多的倍数个数是
1
0
6
p
\dfrac{10^6}{p}
p106,所以枚举每一个质数的倍数为:
1
0
6
2
+
1
0
6
3
+
1
0
6
5
+
.
.
.
.
.
.
1
0
6
p
(
p
为
50000
内
的
质
数
)
\dfrac{10^6}{2}+\dfrac{10^6}{3}+\dfrac{10^6}{5}+......\dfrac{10^6}{p}(p为50000内的质数)
2106+3106+5106+......p106(p为50000内的质数)
1 0 6 ( 1 2 + 1 3 + 1 5 + . . . . . . 1 p ) ( p 为 50000 内 的 质 数 ) 10^6(\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+......\dfrac{1}{p}) (p为50000内的质数) 106(21+31+51+......p1)(p为50000内的质数)
( 1 2 + 1 3 + 1 5 + . . . . . . 1 p ) 的 复 杂 度 大 概 是 l o g l o g n (\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+......\dfrac{1}{p})的复杂度大概是log{log_n} (21+31+51+......p1)的复杂度大概是loglogn所以总复杂度为 n ∗ l o g l o g n ≈ O n n*log{log_n}≈O_n n∗loglogn≈On。
做法:首先找到最小的大于L的p的倍数,然后筛掉区间内所有p的倍数即可。
这里用到一个公式
O
1
求
出
最
小
的
大
于
L
的
p
的
倍
数
O_1求出最小的大于L的p的倍数
O1求出最小的大于L的p的倍数:
l
+
p
−
1
p
\dfrac{l + p - 1}{p}
pl+p−1——上取整公式。
- 如果 L L L% p = 0 p=0 p=0,那么L+p再-1下取整对答案无影响。
- 如果 L L L% p ! = 0 p !=0 p!=0,那么由 L L L% p > = 1 p>=1 p>=1可知 l + p − 1 l + p - 1 l+p−1再除p相当于多了1。
所以枚举的起点为: l + p − 1 p ∗ p \dfrac{l + p - 1}{p}*p pl+p−1∗p。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt;
bool st[N];
void init(int n)
{
memset(st, 0, sizeof st);
cnt = 0;
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 l, r;
while (cin >> l >> r)
{
init(50000);//筛根号n个数
memset(st, 0, sizeof st);
for (int i = 0; i < cnt; i ++ )
{
LL p = primes[i];
for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p)
st[j - l] = true;//用偏移量来筛掉合数可以使得数组不会开太大
}
cnt = 0;
for (int i = 0; i <= r - l; i ++ )
if (!st[i] && i + l >= 2) //合数不能是1
primes[cnt ++ ] = i + l;
if (cnt < 2) puts("There are no adjacent primes.");//如果统计到的质数不足两个
else
{
int minp = 0, maxp = 0;
for (int i = 0; i + 1 < cnt; i ++ )
{
int d = primes[i + 1] - primes[i];
if (d < primes[minp + 1] - primes[minp]) minp = i;
if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
}
printf("%d,%d are closest, %d,%d are most distant.\n",
primes[minp], primes[minp + 1],
primes[maxp], primes[maxp + 1]);
}
}
return 0;
}
198. 反素数
思路:抓住几点性质之后暴搜。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int maxd, number;
int n;
void dfs(int u, int last, int p, int s)//枚举到第几个质数,每个质数递减的指数,当前的这个数是谁,这个数的约数个数
{
if (s > maxd || s == maxd && p < number)//找约数最多且最小的数
{
maxd = s;
number = p;
}
if (u == 9) return;//枚举不超过9种约数
for (int i = 1; i <= last; i ++ )//枚举当前这个约束的每一种指数
{
if ((LL)p * primes[u] > n) break;
p *= primes[u];
dfs(u + 1, i, p, s * (i + 1));
}
}
int main()
{
cin >> n;
dfs(0, 30, 1, 1);//枚举到第几个质数,每个质数递减的指数,当前的这个数是谁,这个数的约数个数
cout << number << endl;
return 0;
}
1273. 天才的记忆
题意:给定一个序列,查询某个区间的最大值。
思路:
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示从
i
i
i 开始,长度是
2
j
2^j
2j的区间中的最值。
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
−
1
]
,
f
[
i
+
2
j
−
1
,
j
−
1
]
)
f[i][j]=max(f[i][j-1],f[i+2^{j-1},j-1])
f[i][j]=max(f[i][j−1],f[i+2j−1,j−1])。
找到一个
k
k
k 使得
2
k
<
=
l
e
n
2^k<=len
2k<=len 的最大的
k
k
k ,那么
2
⋅
2
k
>
l
e
n
2·2^{k}>len
2⋅2k>len,所以枚举
(
l
,
k
)
和
(
r
−
2
k
−
1
,
r
)
(l,k)和(r-2^k-1,r)
(l,k)和(r−2k−1,r)就可以取得这个区间所有的最值情况。
那么如何快速找到使得
2
k
<
=
l
e
n
2^k<=len
2k<=len 的最大的
k
k
k呢:
k
=
[
l
o
g
2
l
e
n
]
向
下
取
整
k=[log_2{len}]向下取整
k=[log2len]向下取整
k
=
[
l
o
g
10
l
e
n
l
o
g
10
2
]
向
下
取
整
k=[\frac{log_{10}len}{log_{10} 2}]向下取整
k=[log102log10len]向下取整
这一步可以使用log函数——返回以10为底的
l
o
g
log
log数。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 200010, M = 18;
int n, m;
int w[N];
int f[N][M];
void init()
{
for (int j = 0; j < M; j ++ )
for (int i = 1; i + (1 << j) - 1 <= n; i ++ )
if (!j) f[i][j] = w[i];
else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r)
{
int len = r - l + 1;
int k = log(len) / log(2);
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
init();
scanf("%d", &m);
while (m -- )
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}
220. 最大公约数
思路:
g
c
d
(
x
,
y
)
=
p
[
质
数
]
,
那
么
显
然
g
c
d
(
x
p
,
y
p
)
=
1
gcd(x,y)=p[质数],那么显然gcd(\dfrac{x}{p},\dfrac{y}{p})=1
gcd(x,y)=p[质数],那么显然gcd(px,py)=1, 此时可以根据互质定义转化为求
1
1
1~
N
p
\dfrac{N}{p}
pN范围内的欧拉函数前缀和。
从图上欧拉函数的分布来看,由
x
,
y
x,y
x,y 相互形成的欧拉函数的和可以表示为
S
n
=
2
⋅
(
φ
2
+
φ
3
+
.
.
.
.
.
.
φ
n
)
+
1
S_n=2·(φ_2+φ_3+......φ_n)+1
Sn=2⋅(φ2+φ3+......φn)+1(1是由对角线上(1,1)这对点形成的) ,之后我们可以求出
S
n
S_n
Sn的前缀和, 使得
O
1
O_1
O1时间查找。
最后再枚举1到N之间所有的质数 p p p,求出所有的 S [ n / p ] S[n/p] S[n/p]的前缀和 。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10;
int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N];
void init(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] * i <= n; j ++ )
{
st[primes[j] * i] = 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 ++ ) s[i] = s[i - 1] + phi[i];//Sn前缀和
}
int main()
{
int n;
cin >> n;
init(n);
LL res = 0;
for (int i = 0; i < cnt; i ++ )//把所有可能的Sn加起来
{
int p = primes[i];
res += s[n / p] * 2 + 1;
}
cout << res << endl;
return 0;
}
AcWing 222. 青蛙的约会
题意:两只青蛙同向围着地球跳,求和时相遇。
注意这里求得的
x
x
x应该是通解,应该把它取模到最小正整数里。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
LL a, b, m, n, L;
cin >> a >> b >> m >> n >> L;
LL x, y;
LL d = exgcd(m - n, L, x, y);
if ((b - a) % d) puts("Impossible");
else
{
x *= (b - a) / d;
LL t = abs(L / d);//把x取模到最小正整数里
cout << (x % t + t) % t << endl;
}
return 0;
}
约数之和【求等比数列】
思路:显然对于一个
A
A
A可以用分解定理:
A
=
p
1
a
1
⋅
p
2
a
2
⋅
.
.
.
.
.
.
⋅
p
k
a
k
A=p_1^{a1}·p_2^{a2}·......·p_k^{ak}
A=p1a1⋅p2a2⋅......⋅pkak,那么对于
A
B
A^B
AB,显然
A
B
=
p
1
a
1
‘
B
⋅
p
2
a
2
⋅
B
⋅
.
.
.
.
.
.
⋅
p
k
a
k
⋅
B
A^B=p_1^{a1`B}·p_2^{a2·B}·......·p_k^{ak·B}
AB=p1a1‘B⋅p2a2⋅B⋅......⋅pkak⋅B。由约数之和的公式可知,约数之和等于一堆括号相乘,每一个括号内部是某个因子从0到因数最高次之和。因为对于一个数 m (
m
<
=
5
e
10
m<=5e10
m<=5e10) 最多只会有
l
o
g
2
m
log_2m
log2m个质因子,几十个,所以括号不多,但是括号内部因为B的范围很大,所以从
0
0
0到
a
i
⋅
B
a_i·B
ai⋅B之间有很多项需要求和。
这里解决很多项求和有两个办法:再去听听快速幂求逆元
1.等比数列求和公式+快速幂求逆元
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=9901;
LL qmi(LL a,LL k)
{
LL res= 1;
while(k)
{
if(k & 1) res=res*a %mod;
a= a*a %mod;
k >>= 1;
}
return res;
}
LL mul(LL p,LL k) //等比数列求和公式,分母转化为求逆元
{ //费马小定理条件
if((p-1)%mod!=0) return (qmi(p,k + 1) - 1 + mod) % mod * (qmi(p - 1,mod - 2) )% mod;
else return k+1%mod;//p-1%mod==0,那么p的每一次幂对应项都会%mod==1,共k+1项
}
int main()
{
LL n,m;
cin>>n>>m;
LL res=1;
for(int i=2;i<=n/i;i++)
{
LL s=0;
if(n%i==0)
{
while(n%i==0)
{
n/=i;
s++;
}
res=res*mul(i,s*m)%mod;
}
}
if(n>1) res=res*mul(n,1*m)%mod;
if(n==0) res=0;
cout<<(res%mod+mod)%mod<<endl;
}
2.分治等比数列+递归
把一个等比数列从中间分开,通过讨论长度,提公因式实现递归。
参数要传数组长度,传长度-1的那个指数下标会出问题。
#include <cstdio>
const int mod = 9901;
int qmi(int a, int k)
{
int res = 1;
a %= mod;
while (k)
{
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int sum(int p, int k)
{
if (k == 1) return 1;
if (k % 2 == 0) return (1 + qmi(p, k / 2)) * sum(p, k / 2) % mod;
return (sum(p, k - 1) + qmi(p, k - 1)) % mod;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
int res = 1;
// 对a分解质因数
for (int i = 2; i * i <= a; i ++ )
if (a % i == 0)
{
int s = 0;
while (a % i == 0)
{
a /= i, s ++ ;
}
res = res * sum(i, b * s + 1) % mod;//第二个参数传长度
}
if (a > 1) res = res * sum(a, b + 1) % mod;
if (a == 0) res = 0;
printf("%d\n", res);
return 0;
}
AcWing 1298. 曹冲养猪
中国剩余定理板子题。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 10;
int n;
int A[N], B[N];
void exgcd(LL a, LL b, LL &x, LL &y)
{
if (!b) x = 1, y = 0;
else
{
exgcd(b, a % b, y, x);
y -= a / b * x;
}
}
int main()
{
scanf("%d", &n);
LL M = 1;
for (int i = 0; i < n; i ++ )
{
scanf("%d%d", &A[i], &B[i]);
M *= A[i];
}
LL res = 0;
for (int i = 0; i < n; i ++ )
{
LL Mi = M / A[i];
LL ti, x;
exgcd(Mi, A[i], ti, x);
res += B[i] * Mi * ti;
}
cout << (res % M + M) % M << endl;
return 0;
}