牛客网Beautiful Trees Cutting(快速幂+逆元+费马小定理)||(快速幂+等比数列求和)

本文详细解析了一道ACM竞赛题目,该题目要求计算字符串中可被5整除的数字组合数量,并提供了两种解决方案:一种是通过等比数列求和的方式,另一种则是利用费马小定理进行计算。
链接: https://www.nowcoder.com/acm/contest/106/B
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld

题目描述


It’s universally acknowledged that there’re innumerable trees in the campus of HUST.


One day Xiao Ming is walking on a straight road and sees many trees line up in the right side. Heights of each tree which is denoted by a non-negative integer from 0 to 9 can form a tree string. It's very surprising to find that the tree string can be represent as an initial string repeating K times. Now he wants to remove some trees to make the rest of the tree string looks beautiful. A string is beautiful if and only if the integer it represents is divisible by five. Now he wonders how many ways there are that he could make it.

Note that when we transfer the string to a integer, we ignore the leading zeros. For example, the string “00055” will be seen as 55. And also pay attention that two ways are considered different if the removed trees are different. The result can be very large, so printing the answer (mod 1000000007) is enough. And additionally, Xiao Ming can't cut down all trees.

输入描述:


 
The first line contains a single integer K , which indicates that the tree string is the initial string repeating K times.
The second line is the initial string S .

输出描述:

A single integer, the number of ways to remove trees mod 1000000007.
示例1

输入

1
100

输出

6

说明

Initially, the sequence is ‘100’. There are
6 ways:
100
1_0
10_
_00
__0
_0_
示例2

输入

3
125390

输出

149796

思路:先不管重复的k次,题目要求求%5==0,那么我只需要保证末尾为5或者是0就可以了,那么遍历所有的位数,遍历到的位数其实是不需要管后面的,也就是123588,我们遍历到5,我们是不需要管后面的88的,所以对于前面的数,我们有C(3,0)+C(3,1)+C(3,2)+C(3,3)==2^3,那么我们就弄出它的本质了,也就是我们只需要遍历出所有位数是0或者5的,然后再算出前面所有的组合情况,例如上面的例子,5的前面有3位数所以,是2^3, 如果是有10位那么就是2^10,依次类推。

解决完一个串的,再去看有k个串的,实际上,我们上面的做法已经将k个串的都遍历完了,我们如果把k个串都放在一起的话当成一个串,利用上面的方法去做,一样能得出答案,但是复杂度太高,于是我们要从一个串去推导,例如说k==3,那么我们解决完第一个串的所有情况,那么对于其它两个串,我们也同样有2^n*2^n的情况(这里的n代表1个串的长度),所以对于第一串的情况,我们就有C*2^n*2^n(C为一个串的组合数),同理我们对第二个串去做也有C*2^n,第三个串有C所以加起来C*(1+2^n+2^(2*n))所以就是一个等比数列

先写等比公式的做法

ll T(int q,int n)
{
    if(n==1) return 1;//return 的是等比数列的a1
    ll date=T(q,n/2);
    date=(date+date*quick(q,n/2))%mod;
    if(n%2) date=(date+quick(q,n-1))%mod;
    return date;
}

//n%2==0  T(n)=T(n/2)+quick(q,n/2)*T(n/2)  这里q是公比
//n%2==1  T(n)=T(n/2)+quick(q,n/2)*T(n/2)+等比数列第n项数值

quick(a,b)为快速幂a^b,然后q为等比数列公比,n为等比数列项数,这是用递归的做法去做的,在T(n)函数中,return 的应该是a1,也就是等比数列首项,我看别人题解写了半天,就是没有弄出它原本的形式,这就让我很蛋疼。

然后就是费马小定理的做法(a/b)%mod,这里我们要用到逆元

乘法逆元定义:
满足a*k≡1 (mod p)的k值就是a关于p的乘法逆元。

为什么要有乘法逆元呢?
当我们要求(a/b) mod p的值,且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元。
我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod p。其结果与(a/b) mod p等价。

证:(其实很简单。。。)
根据b*k≡1 (mod p)有b*k=p*x+1。
k=(p*x+1)/b。
把k代入(a*k) mod p,得:
(a*(p*x+1)/b) mod p
=((a*p*x)/b+a/b) mod p
=[((a*p*x)/b) mod p +(a/b)] mod p
=[(p*(a*x)/b) mod p +(a/b)] mod p
//p*[(a*x)/b] mod p=0
所以原式等于:(a/b) mod p

那么现在就要求这样一个K ,根据已知的两个试子 b*k≡1 (mod p) 和 b^(p-1)≡1 (mod p) (费马小定理)变换下可得

b*k*t1=p*x+1;

b^(p-1)*t2=p*y+1;

两个式子相减,b*k*t1 - b^(p-1)*t2 =p*(x-y)  -----> b*kb^(p-1) mod(p) -------->kb^(p-2)

出处https://blog.youkuaiyun.com/wust_ZJX/article/details/47301743
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int mod=1000000007;
typedef long long ll;
//n%2==0  T(n)=T(n/2)+quick(q,n/2)*T(n/2)  这里q是公比
//n%2==1  T(n)=T(n/2)+quick(q,n/2)*T(n/2)+等比数列第n项数值
ll quick(ll a,ll b)//a^b
{
    ll ans=1;
    while(b!=0)
    {
        if(b%2==1) ans=((ans%mod)*(a%mod))%mod;
        b>>=1;
        a=((a%mod)*(a%mod))%mod;
    }
    return ans;
}
ll T(int q,int n)
{
    if(n==1) return 1;//return 的是等比数列的a1
    ll date=T(q,n/2);
    date=(date+date*quick(q,n/2))%mod;
    if(n%2) date=(date+quick(q,n-1))%mod;
    return date;
}
ll f(ll n,ll k)//(a/b)%mod//费马小定理
{
//(a/b)%mod---->(a*k)%mod
//b*k==1(% p)
//b^(p-1)==1(% p)
//k%p==b^(p-2)%p
    ll b=(quick(2,n))%mod;
    ll a=(quick(b,k))%mod;
    a=(a-1+mod)%mod;
    b=(b-1+mod)%mod;
    return (a*quick(b,mod-2))%mod;
}
char s[100009];
int main()
{
    long long i,k,a,b,q;
    ll ans=0,n;
    scanf("%lld",&k);
    scanf("%s",s);
    n=strlen(s);
    for(i=strlen(s)-1;i>=0;i--)
    {
        if(s[i]=='0'||s[i]=='5')
        {
            ans=(ans+quick(2,i))%mod;
           // printf("%lld\n",ans);
        }
    }
  // q=quick(2,n);
  // a=T(q,k);
   a=f(n,k);
   ans=(ans*a)%mod                                            ;
   printf("%lld\n",ans);
}
里面已经包含两种做法了,把注释去掉就是数列求和做法


### 费马小定理快速幂求解乘法逆元中的应用 费马小定理是数论中的一个重要定理,其描述为:如果 $ p $ 是一个质数,且 $ b $ 不是 $ p $ 的倍数,则有: $$ b^{p-1} \equiv 1 \ (\text{mod} \ p) $$ 基于这一结论,可以推导出 $ b $ 的乘法逆元。具体来说,乘法逆元的定义是:若 $ b \cdot x \equiv 1 \ (\text{mod} \ p) $,则 $ x $ 是 $ b $ 在模 $ p $ 意义下的乘法逆元。根据费马小定理,可以进一步得出: $$ b \cdot b^{p-2} \equiv 1 \ (\text{mod} \ p) $$ 因此,$ b^{p-2} \ \text{mod} \ p $ 就是 $ b $ 的乘法逆元。 在实际计算中,为了高效求解 $ b^{p-2} \ \text{mod} \ p $,通常采用快速幂算法。快速幂通过将指数分解为二进制形式,并利用模运算的性质,将幂次运算的时间复杂度从 $ O(n) $ 降低到 $ O(\log n) $。这种方法特别适合处理大数幂运算,例如在模数 $ p $ 较大的情况下。 以下是一个使用快速幂算法求解乘法逆元的示例代码: ```cpp #include <iostream> using namespace std; // 快速幂算法(取模):a^b % p long long pow_mod(long long a, long long b, long long p) { long long result = 1; a %= p; while (b) { if (b & 1) // 如果b是奇数 result = (result * a) % p; a = (a * a) % p; // 平方 b >>= 1; // 将b右移一位 } return result; } // 求a在模p下的乘法逆元费马小定理,p必须为质数) long long inv(long long a, long long p) { return pow_mod(a, p - 2, p); } int main() { long long a, p; cout << "请输入a和p的值(p必须为质数):" << endl; cin >> a >> p; cout << a << "在模" << p << "下的乘法逆元是:" << inv(a, p) << endl; return 0; } ``` #### 代码说明 - `pow_mod` 函数实现了快速幂算法,用于计算 $ a^b \ \text{mod} \ p $。 - `inv` 函数调用 `pow_mod`,传入参数 $ a $、$ p-2 $ 和 $ p $,以计算 $ a^{p-2} \ \text{mod} \ p $,即 $ a $ 在模 $ p $ 意义下的乘法逆元。 - 在 `main` 函数中,用户输入 $ a $ 和 $ p $ 的值,程序输出对应的乘法逆元。 #### 适用条件 - 使用费马小定理的前提是模数 $ p $ 必须为质数。 - $ a $ 不能是 $ p $ 的倍数,否则无法满足 $ \text{gcd}(a, p) = 1 $,从而无法保证乘法逆元的存在。 通过快速幂算法,可以在 $ O(\log p) $ 的时间复杂度内高效地求解乘法逆元,这在实际应用中具有重要意义,尤其是在密码学和大数运算等领域。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值