A - 快速幂
题目链接:
P1226 【模板】快速幂 - 洛谷
思路:
快速幂使用二进制拆分和乘法倍增的思想,将指数 n 写成二进制,将 a n a^n an 转化为多个 a 2 i a^{2^i} a2i 的乘积。
比如: 3 13 = 3 ( 1001 ) 2 = 3 2 3 + 3 2 2 + 3 2 0 = 3 8 + 3 4 + 3 1 3^{13}=3^{(1001)_2}=3^{2^3}+3^{2^2}+3^{2^0}=3^8+3^4+3^1 313=3(1001)2=323+322+320=38+34+31。
n 有 l o g 2 n + 1 log_2n+1 log2n+1 个二进制位,所以只需要计算出所有的 a 2 i a^{2^i} a2i ,并根据 n 的二进制形式即可求得 a n a^n an 。时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
int qmi(int a,int b,int p)
{
int res=1;
while(b){
//如果b的二进制表示的第0位为1,则乘上当前的a
if(b&1)res=res*a%p;
b>>=1;
//更新a,a依次为a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
a=a*a%p;
}
return res;
}
void solve()
{
int a,b,p;
cin>>a>>b>>p;
cout<<a<<"^"<<b<<" mod "<<p<<"="<<qmi(a,b,p);
}
signed main()
{
int t;
t=1;
while(t--)
{
solve();
}
}
B - 矩阵快速幂
题目链接:
P3390 【模板】矩阵快速幂 - 洛谷
思路:
矩阵乘法 + 快速幂 = 矩阵快速幂 矩阵乘法+快速幂=矩阵快速幂 矩阵乘法+快速幂=矩阵快速幂。
矩阵乘法公式: c [ i ] [ j ] = ( ∑ m = 1 n ( a [ i ] [ m ] ∗ b [ m ] [ j ] ) ) c[i][j]=(\sum^n_{m=1}(a[i][m]*b[m][j])) c[i][j]=(∑m=1n(a[i][m]∗b[m][j]))
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
const int N=110;
int a[N][N];
int ans[N][N];
int t[N][N];
const int MOD=1e9+7;
int n,k;
void multi(int a[][N],int b[][N]){
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int m=1;m<=n;m++)
{
t[i][j]=(t[i][j]+(a[i][m]*b[m][j])%MOD)%MOD;
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
a[i][j]=t[i][j];//把t数组中计算的答案赋值回答案数组
t[i][j]=0;//把t数组清零
}
}
}
void qmi()
{
while(k){
if(k&1) multi(ans,a);
k>>=1;
multi(a,a);
}
}
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
a[i][j]%=MOD;
ans[i][j]=0;
}
ans[i][i]=1;//初始化单位矩阵
}
qmi();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<ans[i][j]<<" ";
}
cout<<endl;
}
}
signed main()
{
int t;
t=1;
while(t--)
{
solve();
}
}
C - 模意义下的乘法逆元
题目链接:
P3811 【模板】模意义下的乘法逆元 - 洛谷
思路:
我选择记忆。
乘法逆元一般用来计算 a / b m o d p a/b\mod\ p a/bmod p,可以先计算出 b 在模 p 意义下的逆元然后再计算。
最后的公式为 x = a p − 2 m o d p x=a^{p-2}\mod p x=ap−2modp。a、p 要互质。
但是这个方法 TLE 了。
换线性递推公式,令 i 的逆元为 inv[i],1 的逆元为 1,我们设 p = k ∗ i + r p=k*i+r p=k∗i+r,k= ⌊ p i ⌋ \lfloor\frac{p}{i}\rfloor ⌊ip⌋, r = p % i r=p\%i r=p%i。
在 mod p 意义下,上式变为 k ∗ i + r = 0 m o d p k*i+r=0 \ mod\ p k∗i+r=0 mod p,左右同时乘 inv[i], inv[r] 得到 k ∗ i n v [ r ] + i n v [ i ] = 0 m o d p k∗inv[r]+inv[i]=0\ mod \ p k∗inv[r]+inv[i]=0 mod p。
所以 i n v [ i ] = − k ∗ i n v [ r ] m o d p inv[i]=-k*inv[r] \ mod \ p inv[i]=−k∗inv[r] mod p 即 i n v [ i ] = − ⌊ p i ⌋ ∗ i n v [ p % i ] m o d p inv[i]=-\lfloor\frac{p}{i}\rfloor*inv[p\%i] \ mod \ p inv[i]=−⌊ip⌋∗inv[p%i] mod p 。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
const int N=3e6+5;
int inv[N];
void solve()
{
int n,p;
cin>>n>>p;
inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=((-p/i)*inv[p%i]%p+p)%p;
}
for(int i=1;i<=n;i++)cout<<inv[i]<<endl;
}
signed main()
{
int t=1;
IOS
while(t--)
{
solve();
}
}
D - 有理数取余
题目链接:
P2613 【模板】有理数取余 - 洛谷
思路:
这个题目其实也是逆元+快速幂,用费马小定理的结论然后推,不需要用高精度,直接在过程中取模。
题目中的输出Angry 其实并没有作用,肯定有解。
代码:
cpp
const int MOD=19260817;
int qmi(int a,int b,int p)
{
int res=1;
while(b){
//如果b的二进制表示的第0位为1,则乘上当前的a
if(b&1)res=res*a%p;
b>>=1;
//更新a,a依次为a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
a=a*a%p;
}
return res;
}
void solve()
{
string s;
cin>>s;
int n=s.size();
int a=0;
for(int i=0;i<n;i++)
{
a=(a*10+(s[i]-'0'))%MOD;
}
cin>>s;
n=s.size();
int b=0;
for(int i=0;i<n;i++)
{
b=(b*10+(s[i]-'0'))%MOD;
}
int ans=1;
if(b==0)cout<<"Angry!";
else{
ans=a*qmi(b,MOD-2,MOD)%MOD;
cout<<ans;
}
}
E - 最大公约数和最小公倍数问题
题目链接:
P1029 [NOIP 2001 普及组] 最大公约数和最小公倍数问题 - 洛谷
思路:
首先 两数乘积 = 最大公约数 ∗ 最小公倍数 两数乘积=最大公约数*最小公倍数 两数乘积=最大公约数∗最小公倍数,所以 P ∗ Q P*Q P∗Q 即为定值只需要枚举 P 来确定 Q 即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
void solve()
{
int x,y;
cin>>x>>y;
int ans=0;
int flag=0;
for(int i=1;i<=x*y/i;i++)
{
if(x*y%i==0&&__gcd(x*y/i,i)==x){
ans++;
if(i*i==x*y)flag=1;
}
}
cout<<ans*2-flag;
}
F - 质数筛
题目链接:
P5736 【深基7.例2】质数筛 - 洛谷
思路:
判断一个数是不是质数就是根据质数的定义来筛选,如果一个数的因数只有 1 和它本身就叫做质数。
代码:
cpp
bool check(int x){
if(x<2)return false;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)return false;
}
return true;
}
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int a;
cin>>a;
if(check(a))cout<<a<<" ";
}
}
G - 线性筛素数
题目链接:
P3383 【模板】线性筛素数 - 洛谷
思路:
上面的太耗时了,如果一个数是已有质数的倍数那么这个数不是质数,这样我们就可以减少很多筛选。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
const int N=1e8+5;
bool st[N];
int n;
int cnt=0;
int p[N];
void get_isPrime()
{
for(int i=2;i<=n;i++)
{
if(!st[i])p[cnt++]=i;
for(int j=0;p[j]<=n/i;j++)
{
st[p[j]*i]=true;
if(i%p[j]==0)break;
}
}
}
void solve()
{
int k;
cin>>k;
cout<<p[k-1]<<endl;
}
signed main()
{
int t;
cin>>n>>t;
IOS
get_isPrime();
while(t--)
{
solve();
}
}
H - 质因子分解
题目链接:
P2043 质因子分解 - 洛谷
思路:
题目的意思是将 N ! N! N! 分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。不用把n的阶乘求出来,只需要一个循环,从2循环到n,把每个数分解的变成1即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
const int N=10001;
bool st[N];
int n;
int cnt=0;
int p[N];
int a[N];
void get_isPrime()
{
for(int i=2;i<=N;i++)
{
if(!st[i])p[cnt++]=i;
for(int j=0;p[j]<=N/i;j++)
{
st[p[j]*i]=true;
if(i%p[j]==0)break;
}
}
}
void solve()
{
cin>>n;
for(int i=2;i<=n;i++)
{
int b=i;
for(int j=0;j<cnt&&p[j]<=b;j++)
{
while(b%p[j]==0){
a[p[j]]++;
b/=p[j];
}
}
}
for(int i=2;i<=n;i++){
if(a[i]!=0){
cout<<i<<" "<<a[i]<<endl;
}
}
}
signed main()
{
int t=1;
IOS
get_isPrime();
while(t--)
{
solve();
}
}
求组合数
组合数的计算: C ( n , k ) = n ! k ! ∗ ( n − k ) ! C(n,k)=\frac{n!}{k!*(n-k)!} C(n,k)=k!∗(n−k)!n!
求组合数 I
当 C a b C^b_a Cab 中 a、b 比较小时可以由公式 C a b = C a − 1 b + C a − 1 b − 1 C^b_a=C^{b}{a-1}+C^{b-1}{a-1} Cab=Ca−1b+Ca−1b−1 递推出来,用的时候直接查表即可。
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl '\n'
const int N=2005;
const int MOD=1e9+7;
int C[N][N];
void init(){
for(int i=0;i<N;i++)
{
for(int j=0;j<=i;j++)
{
if(!j)C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
}
}
void solve()
{
int a,b;
cin>>a>>b;
cout<<C[a][b]<<endl;
}
signed main()
{
int t;
cin>>t;
init();
IOS
while(t--)
{
solve();
}
}
求组合数 II
预处理出阶乘数组和阶乘的逆元数组,再进行运算。模数是质数的情况下可以用费马小定理+快速幂求逆元
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
const int N=1e5+5;
#define endl '\n'
int fact[N],infact[N];
const int MOD=1e9+7;
int qmi(int a,int b,int p)
{
int res=1;
while(b){
if(b&1)res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
void init(){
fact[0]=1,infact[0]=1;
for(int i=1;i<=N;i++)
{
fact[i]=i*fact[i-1]%MOD;
infact[i]=infact[i-1]*qmi(i,MOD-2,MOD)%MOD;
}
}
void solve(){
int a,b;
cin>>a>>b;
int ans=fact[a]*infact[a-b]%MOD*infact[b]%MOD;
cout<<ans<<endl;
}
signed main()
{
int t=1;
cin>>t;
init();
while(t--){
solve();
}
}
求组合数 III
数据范围太大了,所以就不好预处理了。
Lucas 定理: C a b = C a % p b % p ∗ C a / p b / p ( m o d p ) C^b_a=C^{b\%p}{a\%p}*C^{b/p}{a/p}\ (mod \ p) Cab=Ca%pb%p∗Ca/pb/p (mod p)。就不推导了。
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
const int N=1e5+5;
#define endl '\n'
int fact[N],infact[N];
int p;
int qmi(int a,int b,int p)
{
int res=1;
while(b){
if(b&1)res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
int C(int a,int b,int p)
{
if(b>a)return 0;
int res=1;
for(int i=1,j=a;i<=b;i++,j--)
{
res=res*j%p;
res=res*qmi(i,p-2,p)%p;
}
return res;
}
int Lucas(int a,int b,int p){
if(a<p&&b<p)return C(a,b,p);
else{
return C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
}
}
void solve(){
int a,b;
cin>>a>>b>>p;
cout<<Lucas(a,b,p)<<endl;
}
signed main()
{
int t=1;
cin>>t;
while(t--){
solve();
}
}