题目:
问题描述
将一个数N分为多个正整数之和,即N=a1+a2+a3+…+ak,定义M=a1*a2*a3*…*ak为N的潜能。
给定N,求它的潜能M。
由于M可能过大,只需求M对5218取模的余数。
输入格式
输入共一行,为一个正整数N。
输出格式
输出共一行,为N的潜能M对5218取模的余数。
样例输入
10
样例输出
36
数据规模和约定
1<=N<10^18
分析:
我认为这道题最大的难点就在与题目条件没说全,实际上是要求M尽量大的。
好的,那么知道了这个,我们来浅浅的分析一下。
一个数,例如1、2、3、4、5、6、7、8
1是没办法分解的,结果之能为1。
2是分解后就不符合条件的,比如2=1+1,但1*1=1,M太小了,不如不分解。
3和2一样。
4可以分解为1+3,2+2,但1*3=3,2*2=4,所以,我们会把4分解为2+2
5可以分解为,1+4,2+3,我们会分解为2+3
6可以分解为,1+5,2+4,3+3,为了让M更大,其中对5,4进一步分解,结果为1+2+3,2+2+2,3+3,我们会选择3+3,这样M是9
7可以分解为,1+6,2+5,3+4,为了让M更大,其中对4,5,6进一步分解,结果为1+3+3,2+2+3,3+2+2,我们会选择2+2+3,这样M是12
8可以分解为,1+7,2+6,3+5,4+4为了让M更大,其中对4,5,6,7进一步分解,结果为1+3+2+2,2+3+3,3+2+3,2+2+2+2我们会选择3+2+3,这样M是18
......
于是,我们将会发现一个简单的规律,
规律是,如果我们想要M尽量大,就分解这个数,先用3分解。即N/3,看看有几个3,然后看看余数。
若余数N%3==2,则M=pow(3,N/3)*2;
若余数N%3==1,则M=pow(3,N/3-1)*4;
若余数N%3==0,则M=pow(3,N/3)
然后将M对5218取模就完了。
第一版算法代码:
由于M太大,long long int 也不够,所以我们可以牺牲时间来优化空间,即每次乘法后立即对5218取模。于是有了一下第一版算法
(该算法在蓝桥官网时间超限,仅60分)
#include <iostream>
using namespace std;
int main()
{
//已知1<=N<10^18
//那么用long long int 就够了。
long long int n;
scanf("%lld",&n);
int res=1;
int pow=n/3;
if(n==1)
res=1;
else if(n==2)
res=2;
else
{
if(n%3==0)
{
for(int i=0;i<pow;i++)
{
res*=3;
//为了防止res太大,那就每次都对5218取模,反正也没影响
res=res%5218;
}
}
if(n%3==1)
{
for(int i=0;i<pow-1;i++)
{
res*=3;
//为了防止res太大,那就每次都对5218取模,反正也没影响
res=res%5218;
}
res*=4;
res=res%5218;
}
if(n%3==2)
{
for(int i=0;i<pow-1;i++)
{
res*=3;
//为了防止res太大,那就每次都对5218取模,反正也没影响
res=res%5218;
}
res*=2;
res=res%5218;
}
}
printf("%d",res);
return 0;
}
优化算法:
即然时间超了,那么就优化代码。我想到了快速幂运算,因为n非负,所以我们大可以很放心的使用幂运算。
幂运算原理与模板如下:
模板:
int pow(int x, int y)
{
int ans = 1;
while(y)
{
if(y&1) ans = ans*x%p;
x = x*x%p;
y >>= 1;
}
return ans;
}
原理:
看模板来说,对没接触过的人来说可能比较抽象,但实际其实很简单。
1.上述代码ans=1,因为任何数的0次幂为1.
2.就像3^4=9^2一样,3^5=(9^2)*3,我们可以看出,再幂比较大时,我们可以增加底数让幂变小,从而减少幂运算次数,进而提高代码效率。
3.若幂为偶数,则直接底数求平方,幂为原来的一半。若幂为奇数,则幂减1,然后想幂为偶数运算,在最后在乘一个底数。
4.因此,if(y&1)实际是y的二进制数与1相与,即判断y最低为是否为1,这样就能知道y是奇数还是偶数。
5.模运算的一个性质:如果我们对两个数分别取模,然后再将它们的乘积取模,得到的结果与先将它们相乘再取模是相同的。这个性质可以用数学公式表示为:(a * b) % m = ((a % m) * (b % m)) % m,所以,为了防止结果过大,我们在过程中取模,不影响结果的。所以可以有
ans = ans*x%p;
6.y >>= 1;,即将y右移一位,那么y就相当于被除了2.
代码:
#include <iostream>
using namespace std;
int quick_pow(int a,long long int b,int mod)
{
int res=1;
while(b>0)
{
if(b&1)
{
res=res*a%mod;
}
a=a*a%mod;
b=b>>1;
}
return res;
}
int main()
{
//已知1<=N<10^18
//那么用long long int 就够了。
long long int n;
scanf("%lld",&n);
int res=1;
int pow=n/3;
if(n==1)
res=1;
else if(n==2)
res=2;
else
{
if(n%3==0)
{
res=quick_pow(3,n/3,5218);
}
if(n%3==1)
{
res=quick_pow(3,n/3-1,5218);
res=res*4%5218;
}
if(n%3==2)
{
res=quick_pow(3,n/3,5218);
res=res*2%5218;
}
}
printf("%d",res);
return 0;
}