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

链接: 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.csdn.net/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);
}
里面已经包含两种做法了,把注释去掉就是数列求和做法


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值