快速乘+快速幂+矩阵快速幂+素数筛

快速乘

引入:两个不超过 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) 这时将最后一位保存下来,前面82相加可以用上面的方法 
   =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]);
  }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值