2 回答
TA贡献1851条经验 获得超4个赞
没有必要使用,BigInteger
因为:
1*2*3*4*...*N mod M 1+2+3+4+...+N mod M
是相同的
(...(((1*2 mod M)*3 mod M)*4 mod M)...*N mod M) (...(((1+2 mod M)+3 mod M)+4 mod M)...+N mod M)
这应该会加速很多......从(假设的Karatsuba乘法)O(3*N*(n^log2(3)))
和/或加法O(N*n)
到线性O(N)
,其中n
乘数/加法的位宽成比例,并且还有更好的恒定时间......
IIRC 那里还有快速斐波那契计算的公式(转换O(N)
成接近的东西)O(log(N))
这里是朴素 ( ) 和快速(使用 2x2 矩阵平方的幂)算法的C++示例:modfib0
modfib1
//---------------------------------------------------------------------------
int modfib0(int n,int m)
{
for (int i=0,x0=0,x1=1;;)
{
if (i>=n) return x1; x0+=x1; x0%=m; i++;
if (i>=n) return x0; x1+=x0; x1%=m; i++;
}
}
//---------------------------------------------------------------------------
// matrix 2x2: 0 1
// 2 3
void modmul2x2(int *c,int *a,int *b,int m) // c[4] = a[4]*b[4] %m
{
int t[4];
t[0]=((a[0]*b[0])+(a[1]*b[2]))%m;
t[1]=((a[0]*b[1])+(a[1]*b[3]))%m;
t[2]=t[1]; // result is symetric so no need to compute: t[2]=((a[2]*b[0])+(a[3]*b[2]))%m;
t[3]=((a[2]*b[1])+(a[3]*b[3]))%m;
c[0]=t[0];
c[1]=t[1];
c[2]=t[2];
c[3]=t[3];
}
void modpow2x2(int *c,int *a,int n,int m) // c[4] = a[4]^n %m
{
int t[4];
t[0]=a[0]; c[0]=1;
t[1]=a[1]; c[1]=0;
t[2]=a[2]; c[2]=0;
t[3]=a[3]; c[3]=1;
for (;;)
{
if (int(n&1)!=0) modmul2x2(c,c,t,m);
n>>=1; if (!n) break;
modmul2x2(t,t,t,m);
}
}
int modfib1(int n,int m)
{
if (n<=0) return 0;
int a[4]={1,1,1,0};
modpow2x2(a,a,n,m);
return a[0];
}
//---------------------------------------------------------------------------
请注意,为了符合您的限制,所使用的int变量必须至少为 64 位宽!我在旧的 32 位环境中,不想用 bigint 类破坏代码,所以我只用这个进行测试:
int x,m=30000,n=0x7FFFFFFF;
x=modfib0(n,m);
x=modfib1(n,m);
结果如下:
[10725.614 ms] modfib0:17301 O(N)
[ 0.002 ms] modfib1:17301 O(log2(N))
正如你所看到的,快速算法比线性算法快得多...但是对于 Windows 环境来说,测量的时间太短了,而且它的大部分时间很可能是开销而不是函数本身,所以我认为即使是它也应该足够快因为我估计n=10^18它的复杂性是:O(log2(N))
64-31 = 33 bits
0.002 ms * 33 = 0.066 ms
因此,64 位计算的执行时间应该远远低于0.1 ms我的机器(AMD A8-5500 3.2 GHz)上的执行时间,我认为这是可以接受的......
64 位的线性算法如下:
10.725614 s * 2^33 = 865226435999039488 s = 27.417*10^9 years
但正如你所看到的,你早在这之前就已经衰老了......
TA贡献1827条经验 获得超7个赞
为了帮助加快速度,我修改了你的calculatePeriod()
方法。我做了以下几件事。
更改了要记忆的 fib 缓存。它是动态计算的并添加到列表中。如果您反复提示输入值,这会派上用场。那么就不需要重新计算缓存了。
我还添加了一个映射来存储
fibFirst
斐波那契及其模数。我将您的 BigInteger 调用从 更改
new BigInteger(String.valueOf(modulo))
为BigInteger.valueOf(modulo)
。不确定它是否更快,但更干净。最后,我移动了重新计算但在任何循环之外没有更改的值。
这是修改后的方法:
static Map<Long, Map<Long,Long>> fibFirstMap = new HashMap<>();
static List<BigInteger> fibs = new ArrayList<>() {
{
add(BigInteger.ZERO);
add(BigInteger.ONE);
add(BigInteger.ONE);
add(BigInteger.TWO);
}
};
private static long calculateFirst(long nthfib, long modulo) {
long fibFirst =
fibFirstMap.computeIfAbsent(nthfib, k -> new HashMap<>()).getOrDefault(
modulo, -1L);
if (fibFirst > 0L) {
return fibFirst;
}
long periodLength = 0;
boolean periodFound = false;
long[] period = new long[1000000];
period[0] = 0;
period[1] = 1;
period[2] = 1;
int i = 3;
BigInteger cons = BigInteger.valueOf(modulo);
BigInteger nextFib;
while (!periodFound) {
if (i >= fibs.size()) {
fibs.add(fibs.get(i - 2).add(fibs.get(i - 1)));
}
nextFib = fibs.get(i);
period[i] = nextFib.mod(cons).longValue();
if (i == 100000) {
periodFound = true;
periodLength = i - 1;
}
else if (period[i - 1] == 1 && period[i - 2] == 0) {
periodFound = true;
periodLength = i - 2;
}
i++;
}
fibFirst = nthfib % periodLength;
fibFirstMap.get(nthfib).put(modulo, fibFirst);
return fibFirst;
}
更好的方法可能是研究摆脱BigInteger所建议的方法,并寻求基于数论进步的计算改进。
添加回答
举报