带模快速幂详解

博客园的博客

用处

一个能快速求 n k % p n^{k}\%p nk%p的算法,在数论题目里可以用来模拟多次重复的运算,是数论的基础算法。

模板题

题目转载———caioj.cn

【题目描述】

n k % p n^{k}\%p nk%p的值。

【输入格式】

一行,包含三个数 n , k , p n,k,p n,k,p

【输出格式】

一行,输出 n k % p n^{k}\%p nk%p的值。

样例

样例输入

2 6 23

样例输出

18

样例说明

2 6 = 64 , 64 % 23 = 18 2^{6}=64,64\%23=18 26=64,64%23=18

数据范围与提示

对于全部数据, 1 ≤ n , k ≤ 1 0 15 , 1 ≤ p ≤ 1 0 9 1\le n,k\le 10^{15},1 \leq p \leq 10^{9} 1n,k1015,1p109

解析

这是一道基本的数论题,不用数论算法就会超时(简单来说,不用数论思想就是暴力模拟)。
暴力模拟的时间复杂度为 O ( k ) , O(k), O(k), k k k最大可以取 1 0 15 , 10^{15}, 1015,肯定会超时。
快速幂是一种时间复杂度为 O ( l o g 2 ( k ) ) O(log2(k)) O(log2(k))的算法,这代表 k k k甚至可以取到 2 2 × 1 0 8 ( 2 200000000 ) \LARGE2^{\normalsize2\times10^{8}}(2^{200000000}) 22×108(2200000000)
可以说是无限了,C++中很难有这样的时间和内存够这种数运算了 ( F F T (FFT (FFT也做不到呀 ) ) )
为什么说快速幂时间复杂度为为 O ( l o g 2 ( k ) ) , O(log2(k)), O(log2(k)),这因为它的算法模式。
利用 2 2 2进制来分治,将运算次数缩小到二分的次数。
比如数据

2 29 657

n = 2 , k = 29 , p = 657 n=2,k=29,p=657 n=2,k=29,p=657
k k k转化成 2 2 2进制得 11101 , a n s = 1 11101,ans=1 11101,ans=1
每次 n n n都会平方并进行取模,然后将 k ÷ 2 k \div 2 k÷2
因为 n k = ( n × 2 ) k ÷ 2 n^{k}=(n\times2)^{k \div 2} nk=(n×2)k÷2
但是 C C C语言中默认向下取整,所以 5 ÷ 2 = 2 , 5\div 2=2, 5÷2=2,但是还有个 1 1 1该怎么办呢?
所以为什么转化为 2 2 2进制,当 k % 2 = 1 , k\%2=1, k%2=1,代表有一个 1 1 1是多出来的,要先把这 1 1 1 a a a先乘上。
具体代码实现如下

#include<cstdio>
#include<cstring>
using namespace std;
int n,k,p;
int ksm(int a,int b,int inf)
{
	int ans=1;a%=inf;
	while(b)
	{
		if(b&1/*b%2==1*/)ans=ans*a%inf;
		a=a*a%inf;b>>=1;/*b=b/2;*/
	}
	return 0;
}
int main()
{
	scanf("%d%d%d",&n,&k,&p);
	printf("%d\n",ksm(n,k,p));
}

练习题 NOIP2013提高组day1【转圈游戏】

题目描述

n n n个小伙伴 ( ( (编号从 0 0 0 n − 1 ) n-1) n1)围坐一圈玩游戏。按照顺时针方向给 n n n个位置编号,从 0 0 0 n − 1 n-1 n1。最初,第 0 0 0号小伙伴在第 0 0 0号位置,第 1 1 1号小伙伴在第 1 1 1号位置 , … … , ,……, 依此类推。
游戏规则如下:每一轮第 0 0 0号位置上的小伙伴顺时针走到第 m m m号位置,第 1 1 1号位置小伙伴走到第 m + 1 m+1 m+1号位置 , … … , ,……, 依此类推,第 n − m n−m nm号位置上的小伙伴走到第 0 0 0号位置,第 n − m + 1 n-m+1 nm+1号位置上的小伙伴走到第 1 1 1号位置 , … … , ,……, n − 1 n-1 n1号位置上的小伙伴顺时针走到第 m − 1 m-1 m1号位置。
现在,一共进行了 1 0 k 10^{k} 10k轮,请问 x x x号小伙伴最后走到了第几号位置。

输入格式

输入共 1 1 1行,包含 4 4 4个整数 n , m , k , x , n,m,k,x, n,m,k,x,每两个整数之间用一个空格隔开。

输出格式

输出共 1 1 1行,包含 1 1 1个整数,表示 1 0 k 10^{k} 10k轮后 x x x号小伙伴所在的位置编号。

样例

样例输入1

10 3 4 5

样例输出1

5

数据范围与提示

对于 30 % 30\% 30%的数据 , 0 &lt; k &lt; 7 ; ,0 &lt; k &lt; 7; ,0<k<7
对于 80 % 80\% 80%的数据 , 0 &lt; k &lt; 1 0 7 ; ,0 &lt; k &lt; 10^{7}; ,0<k<107;
对于 100 % 100\% 100%的数据 , 1 &lt; n &lt; 1 0 6 , 0 &lt; m &lt; n , 1 ≤ x ≤ n , 0 &lt; k &lt; 1 0 9 。 ,1 &lt; n &lt; 10^{6},0 &lt; m &lt; n,1 \leq x \leq n,0 &lt; k &lt; 10^9。 ,1<n<106,0<m<n,1xn,0<k<109

题解

这道题不难可以推出 ( x + m × 1 0 k ) % n (x+m\times10^{k})\%n (x+m×10k)%n
根据模运算的的分配率可以得到 ( x % n + m % n × 1 0 k % n ) % n (x\%n+m\%n\times10^{k}\%n)\%n (x%n+m%n×10k%n)%n只需用快速幂求出 1 0 k 10^{k} 10k就可以了
A C AC AC代码

#include<cstdio>
#include<cstring>
using namespace std;
int n,m,k,x;
int ksm(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%n; 
		a=a*a%n;b>>=1;
	}
	return ans;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&x);
	printf("%d\n",(x%n+m%n*ksm(10,k)%n)%n);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值