问题描述
将一个数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
这道题有两个问题需要解决。
其一,如何分解这个数。此处我枚举了10以内的分解情况:
数 | 分解 | 积 |
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 3 |
4 | 2+2 | 2*2=4 |
5 | 2+3 | 2*3=6 |
6 | 3+3 | 3*3=9 |
7 | 2+2+3 | 2*2*3=12 |
8 | 2+3+3 | 2*3*3=18 |
9 | 3+3+3 | 3*3*3=27 |
10 | 2+2+3+3 | 2*2*3*3=36 |
这里不给证明地给出观察结果,任给一个数x>1,能够分解为x=x1+x2+x3···+xn(n为整数且n≥1)的形式,当且仅当任意因子xi≠1(1≤i≤n)且有最多的因子=3时,其乘积最大。(此时因子2不多于两个)
根据上述结论,我们可以预测11=2+3+3+3,积2*3*3*3=54为最大。
因此,对于任给的n,我们整除能得到因子3的个数。为了防止因子1出现,需要对3取余,若结果为1,则“返还”一个因子3凑成两个因子2。即为解。
至此,第一个问题已经得到解决。
注意到输入规模达到了10^18的量级,如果进行暴力连乘取余,也达到10^6的量级,很可能也时难以承受的,经过测试果不其然。
因此需要考虑如何减小这一运算规模。注意到输出结果要求对5218这样一个并不是很大的数取余。根据鸽巢原理可知,在不多于5219次操作中,必然会出现循环。此处我再详细解释一下鸽巢原理的应用,假设对3的连乘和取余操作达到5218次后,所有余数都已出现恰好一次,那么下一次必然与之前发生重复。重复意味着什么呢?一旦重复,所得到的值在进行相同的若干次操作后,必然沿着原来的“轨迹”再次回到这个值。此处考虑的是最坏情况需要5219次操作,事实上,很可能在某一步骤已经重复,一旦出现环,后续的运算就不再必要了。我们只需要计算出环的长度,令操作次数对这个长度取余就可以免去不必要的运算。
要注意,出现环时不一定会回到第一个元素,换句话说,可能有一条“尾巴”,这个尾巴的长度也应该考虑在内,实际操作时,需要将操作数与尾长比较,如果小于尾长,就来不及进入环了;如果大于尾长,则应该先减去尾长再对环长取余。
虽然代码实现时,我省略掉了得出这个结果的过程,不过此处有必要将具体细节说明:
声明一个整型数组,长为5218,初始化为0,数组下标即为余数标识,声明一个整型变量初始化为0用于记录路径长。开始时,访问1号下标,如果值为0,则将其标记为当前位次,此时是0。将路径长+1,并访问下一个结点。当访问到一个结点不为0时,其下标即为尾长,而路径长-尾长+1即为环长。
实际求解时,非常巧合的是第一个元素就是循环点,换句话说,整个过程都在一个环中,没有尾部,其长度为2608。得到这个长度,整个的判环操作就完成使命了,接下来只需声明一个长度为2608的整型数组依次迭代打表即可。
接下来就很容易了,根据前面的方法得到因子3的个数,就是操作数,将其对2608取余,直接从表中读取结果,这样一来,时间复杂度已经降低为常数级了。最后还有1~2个因子2需要处理,别忘了。
代码如下:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main(void){
long long n,s,a,b=0;
int *m=(int*)malloc(sizeof(int)*2608);
for(int i=0,t=1;i<2608;i++){
m[i]=t;
t=(t*3)%5218;
}
cin>>n;if(n==1)n--;
a=n/3;
switch(n%3){
case 1:if(a--)b=2;break;
case 2:b=1;
}
s=m[a%2608];
for(long long i=0;i<b;i++)s=(s*2)%5218;
cout<<s;
return 0;
}