快速乘
引入:两个不超过 long long 的数 a、b 相乘,求对 p 取模后的结果
两个大数直接相乘会存在直接爆掉的情况
方法一:将乘法变成加法
在加法的过程,保证不会爆掉。
快速乘是将加法变得更高效
但偶数个相加时,如2*8=2+2+2+2+2+2+2+2 可以变成4+4+4+4(相加次数变为原来的一半) 再可以变成 8+8 这就大大的降低相加的次数
比如2*9
2*9=2+2+2+2+2+2+2+2+2 (b=9) 这时将最后一位保存下来,前面8个2相加可以用上面的方法
=4+4+4+4+2 (b=4)
=8+8+2 (b=2)
=16+2 (b=1)
=18
typedef long long LL;
LL quick_mul(LL a,LL b,LL mod){
LL res=0;
while(b){
if(b&1){
res=(res+a)%mod;
}
a=(a+a)%mod;
b>>=1;
}
return res;
}
方法二:通过 long double 直接算
long double 可以表示 -10^(304) 到 +10^(304) 之间的数
用long double 类型来计算a*b/mod的值,防止溢出
typedef long long LL;
LL a,b,mod;
LL quick_mul(LL a,LL b.LL mod){
LL tmp=LL(a*(long double)b /mod); //因为long double无法取模所以转换为long long
return ((a*b-tmp*mod)+mod)%mod;
/* a*b和tmp*mod都可能溢出,long long 有自动溢出,所以他两的差值不变(也就是如果a*b/mod溢出了,那a*b也是溢出的,溢出了两个都变到long long负的那边去了,所以他两的差值还是一样的)所以溢出也不会影响结果
不管他溢出还是不溢出,两个相减只是一个余数或者是余数-mod,并且是在long long的范围内, 所以要在后面加个mod再余个mod */
}
快速幂
需要计算 a 的 n 次方,模拟乘法会超时
本质和快速乘比较类似 是减少一般的乘方运算中的重复部分
比如2^8=2 * 2 * 2 * 2 * 2 * 2 * 2 * 2
先计算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 (黑体部分)
再计算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2
最后计算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2
三步完成
typedef long long LL;
LL poww(LL a,LL n){
LL res=1;
while(n){
if(n&1) res=(res*a)%mod;
a=(a*a)%mod;
n>>=1;
}
return res;
}
问题 E: 小小粉刷匠(完整船星版本)
时间限制: 1 Sec 内存限制: 128 MB1]
题目描述
在山的那边有一群快乐的小粉刷匠, 他们喜欢粉刷一切物品。
某天村长从山的另一头拿来了一块木板,希望寻找村里粉刷技术最高超的粉刷匠来刷它。
海廷和明昊是村里粉刷技术最高超的粉刷匠,村长打算让他们来进行粉刷,这时候他们对粉刷方案出现了分歧,最后决定从所用能用的粉刷方案中重新进行选择,你路过的时候他们希望你能帮忙计算总共有多少种粉刷方案。
木板为一块纯白色木板,上面分布着2*n(2<= n <= 10^18)个格子,但是作为粉刷匠的习惯左上角和右下角的格子一定要涂黑,粉刷匠只能使用黑色颜料去选择粉刷剩余白色格子,每一列至少要粉刷一格, 现在需要你计算总共有多少种粉刷方案。
粉刷方案可能会过大, 所以对最后的总数求模 1000000009
当n = 2时, 木板如上图, 粉刷方案一共4种
输入
本题有多组测试样例
每一组样例第一行输入一个n (2 <= n <= 10^18)
输出
输出涂色方案总数,总数求模1000000009
样例输入
2
3
21
样例输出
4
12
649045832
左上角第一格和右下角最后一格已经被粉刷了,当N = 2时, 粉刷方案共四种:
使用快速幂
除两边以外,中间的每一列有三种情况
两边有四种情况
#include<bits/stdc++.h>
const int mod=1000000009;
typedef long long LL;
LL poww(LL a,LL n){
LL res=1;
while(n){
if(n&1) res=(res%mod*a%mod)%mod;
a=(a%mod*a%mod)%mod;
n>>=1;
}
return res;
}
int main() {
long long n;
while(scanf("%lld",&n)!=EOF){
long long ans=1;
ans=poww(3,n-2);
printf("%lld\n",(ans%mod*4%mod)%mod);
}
return 0;
}
矩阵快速幂
场景:
需要计算矩阵 A 的 n 次方,模拟乘法会超时
矩阵相乘
struct Martrix{
int m[maxn][maxn];
};
Martrix mul(Martrix a,Martrix b){
Martrix ans;
for(int i=0;i<10;i++)
for(int j=0;j<10;j++){
ans.m[i][j]=0;
for(int k=0;k<10;k++){
ans.m[i][j]+=((a.m[i][k]%mod)*(b.m[k][j]%mod))%mod;
ans.m[i][j]%=mod;
}
}
return ans;
}
一般还要结合快速幂
问题 C: 求斐波那契数列
题目描述
斐波那契数列的定义如下:
F(0) = 0
F(1) = 1
F(n) = F(n - 1) + F(n - 2) (n >= 2)
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 …)
给出n,求F(n),由于结果很大,输出F(n) % 1000000009的结果即可。
输入
输入1个数n(1 <= n <= 10^18)。
输出
输出F(n) % 1000000009的结果。
样例输入
11
样例输出
89
分析:
使用矩阵快速幂
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000009;
typedef long long LL;
struct Martrix{
LL m[2][2];
};
Martrix Mul(Martrix a,Martrix b){
Martrix tmp;
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
tmp.m[i][j]=0;
for(int k=0;k<2;k++){
tmp.m[i][j]+=((a.m[i][k]%mod)*(b.m[k][j]%mod))%mod;
tmp.m[i][j]%=mod;
}
}
}
return tmp;
}
Martrix poww(Martrix a,Martrix b,LL n){
while(n>0){ //n>0 !!!!!!!!!!!!!!!!!!!!!
if(n&1){
b=Mul(a,b);
}
a=Mul(a,a);
n>>=1;
}
return b;
}
int main(){
LL n;
Martrix a,b,tmp;
a.m[0][0]=1;a.m[0][1]=1;
a.m[1][0]=1;a.m[1][1]=0;
b.m[0][0]=1;b.m[0][1]=0;
b.m[1][0]=1;b.m[1][1]=0;
scanf("%lld",&n);
tmp=poww(a,b,n-2);
printf("%lld\n",tmp.m[0][0]);
}
问题 D: 一个简单的数学问题
时间限制: 1 Sec 内存限制: 128 MB
提交: 66 解决: 18
[提交][状态][讨论版][命题人:201506020818]
题目描述
函数 f(x).
若 x < 10 f(x) = x.
若x >= 10 f(x) = a0 * f(x-1) + a1 * f(x-2) + a2 * f(x-3) + …… + a9 * f(x-10);
ai(0<=i<=9) 只能为 0 或 1 .
已知 a0 ~ a9 和两个正整数 k 与 m 计算 f(k)%m.
输入
问题包含多个测试用例。
在每种情况下,都有两行。
在第一行中有两个正整数k和m。(k < 2 * 10 ^ 9 m < 10 ^ 5)
在第二行中,有10个整数代表a0 ~ a9
输出
每个样例在一行里输出 f(k) % m
样例输入
10 9999
1 1 1 1 1 1 1 1 1 1
20 500
1 0 1 0 1 0 1 0 1 0
样例输出
45
104
分析:
#include<bits/stdc++.h>
using namespace std;
struct Martrix{
int m[10][10];
}Move,start;
int k,mod;
Martrix mul(Martrix a,Martrix b){
Martrix ans;
for(int i=0;i<10;i++)
for(int j=0;j<10;j++){
ans.m[i][j]=0;
for(int k=0;k<10;k++){
ans.m[i][j]+=((a.m[i][k]%mod)*(b.m[k][j]%mod))%mod;
ans.m[i][j]%=mod;
}
}
return ans;
}
Martrix poww(Martrix a,int n){
Martrix ans;
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
if(i==j){
ans.m[i][j]=1;
}
else ans.m[i][j]=0;
}
}
while(n){
if(n&1) ans=mul(ans,a);
a=mul(a,a);
n>>=1;
}
return ans;
}
int main(){
while(scanf("%d %d",&k,&mod)!=EOF){
memset(Move.m,0,sizeof(Move.m));
for(int i=0;i<10;i++){
scanf("%d",&Move.m[0][i]); //初始化转移矩阵
if(i+1<10)
Move.m[i+1][i]=1;
start.m[9-i][0]=i;
}
if(k<10) printf("%d\n",k);
else{
Martrix ans=poww(Move,k-9);//相乘k-9次
int temp=0;
for(int i=0;i<10;i++){
temp+=(ans.m[0][i]*start.m[i][0])%mod;
temp%=mod;
}
printf("%d\n",temp);
}
}
return 0;
}
筛法求素数
一、埃拉特斯特尼筛法
原理:
当一个数是素数的时候,那么他的倍数肯定是合数。
时间复杂度为 O(nloglogn)。
int isprime[maxn];//记录素数的数组;
int vis[maxn];//素数标记数组,0是素数,1是合数
void Prime(int n){ //
int cnt=0;
memset(vis,0,sizeof(vis));
vis[0]=1;
vis[1]=1;
for(int i=2;i<n;i++){
if(!vis[i]{//如果是素数
isprime[cnt++]=i;
for(int j=i*i;j<n;j+=i){//如果是素数,那他的倍数全是合数,把他的倍数全标记为合数
vis[j]=1;
}
}
}
}
欧拉筛
原理:
首先,任何合数都能表示成一系列素数的积。然后利用了最小的素数因子,每个合数仅被它的最小素因子筛去正好一次。
时间复杂度为 O(n)。
nt vis[maxn],isprime[maxn];
void Prime(){
int cnt=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<maxn;i++){
if(!vis[i])
isprime[cnt++]=i;
for(int j=0;j<cnt&&i*isprime[j]<maxn;j++){ //j遍历素数数组,用i去乘素数数组里的素数,并标记为合数
vis[i*isprime[j]]=1;
if(i%isprime[j]==0)
break;
}
}
}
问题 A: 素数查询(Easy)
题目描述
给定一个数,判断这个数是不是素数。
输入
每行输入一个数 n ,输入到 0 停止。( 2 <= n <= 200000)
输出
如果这个数是素数,输出 “Yes”,否则输出 “No” 。
样例输入
5
999
57
200000
0
样例输出
Yes
No
No
No
#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+5;
int vis[maxn],isprime[maxn];
void Prime(){
int cnt=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<maxn;i++){
if(!vis[i])
isprime[cnt++]=i;
for(int j=0;j<cnt&&i*isprime[j]<maxn;j++){
vis[i*isprime[j]]=1;
if(i%isprime[j]==0)
break;
}
}
}
int main(){
int n;
Prime();
while(cin>>n&&n){
//for(int i=0;i<100;i++)
//cout<<i<<":"<<vis[i]<<" ";
// cout<<endl;
if(vis[n]==0)
cout<<"Yes"<<endl;
else if(vis[n]==1)
cout<<"No"<<endl;
}
}
问题 B: 素数查询(Hard)
题目描述
给定两个整数,输出这两个整数之间素数的个数。
输入
输入第一行是一个整数 T,( 1 <= T <= 100000 )
接下来 T 行每行输入两个整数 a 和 b。( 1 < a <= b <= 200000 )
输出
对于每组询问输出区间 [ a, b ] 里素数的个数。
样例输入
5
2 2
3 999
67 68
2 19999
998 999
样例输出
1
167
1
2262
0
用一个pre数组记录i之前的数组有多少个
#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+5;
int vis[maxn],isprime[maxn],pre[maxn];
void Prime(){
int cnt=0,cont=0;
memset(vis,0,sizeof(vis));
for(int i=2;i<maxn;i++){
if(!vis[i])
isprime[cnt++]=i;
pre[i]=cnt;
for(int j=0;j<cnt&&i*isprime[j]<maxn;j++){
vis[i*isprime[j]]=1;
if(i%isprime[j]==0)
break;
}
}
}
int main(){
int n,t,a,b,cont;
Prime();
scanf("%d",&t);
while(t--){
//for(int i=0;i<100;i++)
//cout<<i<<":"<<vis[i]<<" ";
// cout<<endl;
cont=0;
scanf("%d %d",&a,&b);
printf("%d\n",pre[b]-pre[a-1]);
}
}