快速幂求逆元

逆元的定义:

当 a m ≡ 1(mod p), m即为 a 在mod p 意义下的逆元。(需要注意只有a和p互质,a才有关于p的逆元)。

公式推导:

a * b % p ≡ a * m % p;(两边同时乘以b,再除以a)。

1 % p ≡ m * b % p(m为满足这个条件的最小的一一个数,我们就将其称之为b的逆元)。

费马小定理

由费马小定理可知:对于任意的质数 p ,任意a都满足b^{p} ≡ a,化简可得:b * b^{p - 2}  ≡ b。与前面的逆元公式一一对应可得,m == b^{p - 2}

为什么除法要用逆元?

a * b % p == ( ( a % p ) * ( b % p ) ) % p

a / b % p != ( ( a % p ) / ( b % p ) ) % p

在除法运算中不可以对分子分母分别取模来进行计算,当 a 和 b 足够大时如果强行分别取模的话会导致精度缺失较大,结果就不准确了。

例题:牛客--小y的树

题目描述:

求一颗 n 层的满 k 叉树,求任意两点之间距离和等于多少,答案对10^{9} + 7取模。
树上两点距离: 沿着最短路径从 u 走到 v 经过的边的数量。
n 层的满 k 叉树:叶子节点到根的距离是 n − 1,每个点都有 k 个儿子。

输入描述:

一行两个正整数 n, k(2 <= n, k <= 10^{6})。

输出描述:

输出一行一个整数代表答案。

示例:

3 2

48

思路:

我们算总共走过的边的数量,换而言之就是要我们算每条边会被走多少次,每次我们可以以一条边v为分割线将树分成两个部分A, B分别有a,b 个点,计算从A(B)出发走完所有的B (A) 的点会经过多少次 v (很显然 a * b 次),然后计算v那一条边同一层会有多少条边 V,故V层会贡献 a * b * V 次。(注意v和V的区别)

 这个题的点的计算方法我知道的有两种:

1.递推(这种这里不作详细解释)

2.利用等比数列求和公式,这里涉及到乘法快速幂求逆元了。

  •  首先给出求和公式 : a1 * (1 - q^{n}) / (1 - q) (mod p)。
  • 用逆元除法变乘法 :a1 * (1 - q^{n}) * (1 - q)^{ p -2}(mod p)。
  • 还有一个需要注意的一点,取模情况一旦涉及减法就一定要先加上模数再进行取模操作

AC代码: 

#include<iostream>
#include<cstdio>
using namespace std;

typedef long long ll;

const int N = 1e6 + 7, mod = 1e9 + 7;


ll ksm(ll a,ll b,ll p)
{
	ll ans = 1;
	while(b)
	{
		if(b & 1) ans = ans * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return ans;
}

void solve()
{
	ll n, k;
	ll ans = 0, sum, x = 1, y1, y2;
	cin >> n >> k;
	
	sum = -1 * (1 - ksm( k, n, mod)) * ksm( k - 1, mod - 2, mod) % mod;//求逆元
	
	for(int i = 1;i < n;i ++)
	{
		x = x * k %  mod;
		y1 = -1 * (1 - ksm(k,n - i, mod)) * ksm( k - 1, mod - 2, mod) % mod;
		y2 = ((sum - y1) % mod + mod) % mod;
		
	    ans = (ans + (((y1 * y2) % mod) * x) % mod) % mod ;
	}
	cout << ans <<endl;
}

int main()
{
	solve();
	return 0;
}

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值