欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Sum (HDU - 4704)隔板定理 组合数求和 费马小定理(2^(N-1)%(1e9+7) N巨大)

程序员文章站 2022-06-06 22:26:48
...

                                                               Sum

                         Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
                                          Total Submission(s): 4338    Accepted Submission(s): 1765

 

Problem Description

Sum (HDU - 4704)隔板定理 组合数求和 费马小定理(2^(N-1)%(1e9+7) N巨大)

 

Sample Input

2

 

Sample Output

2

 

Hint

1. For N = 2, S(1) = S(2) = 1. 2. The input file consists of multiple test cases.

 

Source

2013 Multi-University Training Contest 10


题解

此题的大意是:给出一个N,得出N被分成1、2、3 . . . N个数的情况之和,然后对1e9+7取余

以4为例:S(1) = 1(4)、S(2) = 3 (1+3,3+1,2+2)、S(3) = 3(1+1+2, 1+2+1,2+1+1)、

S(4) = 1(1+1+1+1)。所以N=4时求的是 (1+3+3+1)%(1e9+7)

那么问题来了,这个S(1)+S(2)+. . . +S(N)怎么求呢?

这里就联系到高中(还是初中来着。。忘了)数学知识——隔板定理,如下图所示

Sum (HDU - 4704)隔板定理 组合数求和 费马小定理(2^(N-1)%(1e9+7) N巨大)

N=4可以看成4个小球,S(1)就是将这4个小球分成一组,也就是在4个小球中间的三个空隙中不插板子,组合数为C(3,0);S(2)就是将这4个小球分成两组,也就是插一个板子,组合数为C(3,1)。同理S(N)就是插N-1个板子(分成N组),组合数为C(N-1,N-1)。

下面的问题就是怎么求他们的和了,数学符号有点难打。。就手写了,字丑凑乎看吧。。。

Sum (HDU - 4704)隔板定理 组合数求和 费马小定理(2^(N-1)%(1e9+7) N巨大)

简化了很多有木有,那么新的问题来了,题上可知N最大为10的1000000次,而longlong最多也就10的18次(大概。。记不清了),那怎么求这超级无敌雷霆霹雳大的次幂呢?

费马小定理登场!

定理内容:假如p是质数,且gcd(a,p)=1(也就是a不为p的倍数),那么 a^(p-1)≡1(mod p)

由题可知,1e9+7是个质数,2是整数,a与p互质显而易见,所以现在我们的目的就是想办法把2^(N-1)%(1e9+7)降幂为2^k%(1e9+7)

已知a^(p-1) = 1,且n可能很大很大,就看N-1里包括多少个p-1,把这些都丢掉求剩下的就好。假设有x个p-1(p=1e9+7)则:2^(N-1) = 2^(x*(p-1)) * 2^k = 1^x * 2^k = 2^k,所以直接求2^k就好,k = (N-1)%(p-1)

由于N过于长,就用字符串存储,之后边转化为数边取余,最后减1即可。

还有就是处理过后的N也不小,求次幂时用快速幂会减很多不必要的重复时间

# include <cstdio>
# include <cstring>
# include <algorithm>
# define mod 1000000007

using namespace std;

typedef long long ll;
const int maxn = 1000000;
char n[maxn];
 
ll quick_pow(ll a, ll k) 
{
    ll ans=1;
    while(k)
    {
        if(k&1)
        {
            ans=(ans*a)%mod;
            k--;
        }
        k/=2;
        a=a*a%mod;
    }
    return ans;
}

int main()
{
	while(~scanf("%s",n))
	{
		ll len = strlen(n);
		ll mod1 = mod-1;
		ll k = n[0]-'0';
		for(ll i=1;i<len;i++) k = (k*10 +(n[i]-'0')) % mod1;
		
		k -= 1;
		printf("%lld\n",quick_pow(2,k));
	}
	return 0;
}