欢迎来到某某官网,某某是一家生产阳光板耐力板厂家,品质保障,欢迎咨询!

返回列表页

关于算法竞赛中快速乘的一些优化

在一些算法竞赛题目中经常遇到一些需要取模的乘法运算,一般都是会爆 C++long\\;long 范围的。

以下的计算均只考虑正整数的情况。

所以有人按着快速幂的思想提出了 O(logW)W 是乘数)的快速乘。

inline long long mmul ( long long a, long long b, const long long& Mod )  {
	long long rt ( 0 ) ;
	for ( ; b ; b >>= 1, ( a <<= 1 ) %= Mod )
		if ( b & 1 )  {
			( rt += a ) %= Mod ;
		}
	return rt ;
}

复杂度好高吖!!!


也有加以思考提出 O(1) 的使用大范围的long\\;double 的快速乘的。

inline long long mmul ( long long a, long long b, const long long& Mod )  {
    a %= Mod, b %= Mod ;
    return ( a * b - ( long long ) ( ( ( long double ) a * b + 0.5 ) / Mod ) * Mod ) % Mod ;
}

对于模数大于 \\sqrt{LLong\\_MAX} 的,它也会爆掉(计算 a\\cdot b 爆掉)。

但是上面的方法都有时间复杂度过高或者是常数大的缺点。

为什么不使用小学知识的乘法分配律来优化呢?


我们可以考虑提出一个把大数拆成两个数之和的形式,使得可以在 long\\;long 范围内计算。

我们要计算 a \\cdot b\\;mod\\;p

b=lf+rg

那么原式为

a\\cdot(lf+rg)\\;mod\\;p=((a\\cdot lf)\\;mod\\;p+(a\\cdot rg)\\;mod\\;p)\\;mod\\;p

我们把 lf 钦定为 b 的二进制前 x 位, rgb 的后 (64-x) 位。

x 根据实际情况来定吧,实在不行把 a,b 都拆了,可以应对模数大于 \\sqrt{LLong\\_MAX} 的情况。


参考代码:

inline long long mmul ( long long a, long long b, const long long& Mod )  {
	long long lf = a * ( b >> 25LL ) % Mod * ( 1LL << 25 ) % Mod ;
        long long rg = a * ( b & ( ( 1LL << 25 ) - 1 ) ) % Mod ;
	return ( lf + rg ) % Mod ;
}

我随机了 1e7 组数据,数据范围均为 1e10 以内(对于第二种方法可能小小有失公正)。

使用环境(机房电脑)为:

  • i5-4590 @3.30GHz
  • 4GB 内存
  • Windows 7系统(这个机房没有Linux系统......)
  • G++ 4.9.2

编译命令:

-O2 -std=c++11 -Wl,--stack=80000000 -Wall -Wconversion -Wextra


这是生成数据的 generator

# include <bits/stdc++.h>

long long r ( )  {
	return ( rand ( ) << 15LL | rand ( ) ) << 16LL | ( ( rand ( ) << 15LL | rand ( ) ) ) ;
}

int main ( )  {
	srand ( time ( 0 ) ) ;
	freopen ( "in.txt", "w", stdout ) ;
	int T = 10000000 ;
	const long long Mod = 10000000000LL ;

	while ( T -- )  {
		long long a = r ( ) % Mod, b = r ( ) % Mod ;
		if ( a < 0 )  a += Mod ;
		if ( b < 0 )  b += Mod ;
		assert ( a >= 0 && b >= 0 ) ;
		printf ( "%lld %lld\
", a, b ) ;
	}
	return 0 ;
}


这是测试用的 cpp 文件:

# include <bits/stdc++.h>

inline long long mmul1 ( long long a, long long b, const long long& Mod )  {
	long long lf = a * ( b >> 25LL ) % Mod * ( 1LL << 25 ) % Mod ;
    long long rg = a * ( b & ( ( 1LL << 25 ) - 1 ) ) % Mod ;
	return ( lf + rg ) % Mod ;
}

inline long long mmul2 ( long long a, long long b, const long long& Mod )  {
	long long rt ( 0 ) ;
	for ( ; b ; b >>= 1LL, ( a <<= 1LL ) %= Mod )
		if ( b & 1 )  {
			( rt += a ) %= Mod ;
		}
	return rt ;
}

inline long long mmul3 ( long long a, long long b, const long long& Mod )  {
    a %= Mod, b %= Mod ;
    return ( a * b - ( long long ) ( ( ( long double ) a * b + 0.5 ) / Mod ) * Mod ) % Mod ;
}

int main ( )  {
	freopen ( "in.txt", "r", stdin ) ;
	freopen ( "result.txt", "w", stdout ) ;
	int t1 ( 0 ), t2 ( 0 ), t3 ( 0 ) ;
	long long a, b, ans1, ans2, ans3 ;
	# define MOD 1000000007LL
	while ( ~ scanf ( "%lld%lld", & a, & b ) )  {
		int start = clock ( ) ;
		ans1 = mmul1 ( a, b, MOD ) ;
		t1 += clock ( ) - start ;
		start = clock ( ) ;
		ans2 = mmul2 ( a, b, MOD ) ;
		t2 += clock ( ) - start ;
		start = clock ( ) ;
		ans3 = mmul3 ( a, b, MOD ) ;
		t3 += clock ( ) - start ;
	//	fprintf ( stderr, "%lld %lld %lld\
", ans1, ans2, ans3 ) ;
		assert ( ans1 == ans2 && ans2 == ans3 ) ;
	}
	# undef MOD
	puts ( "test result :" ) ;
	printf ( "mmult1 : %.3lfs\
", 1.0 * t1 / CLOCKS_PER_SEC ) ;
	printf ( "mmult2 : %.3lfs\
", 1.0 * t2 / CLOCKS_PER_SEC ) ;
	printf ( "mmult3 : %.3lfs\
", 1.0 * t3 / CLOCKS_PER_SEC ) ;
	return 0 ;
}


为什么不在测试程序里直接随机,因为不好构造数据,输入慢没关系了。


在有 O2 的优化下:

test result :
mmult1 : 0.132s
mmult2 : 1.352s
mmult3 : 0.135s

可能因为有 O2 ,优势相对于 long\\;double,几乎没有的。但优势相对于带log 确实十分优秀。


于是又测试了没有 O2 的情况:

test result :
mmult1 : 0.522s
mmult2 : 6.214s
mmult3 : 0.542s

emmmmmm,好像还是差不多。

不管反正我以前在老年机上跑出来第一种方法的时间是第三种的二分之一( CCF 那种老年机)。


  • log 的快速乘不要写了,慢。
  • long\\;double 的虽然很快,但有可能掉精度吖,而且对于模数大于 \\sqrt{LLong\\_MAX} 的,它也会爆掉。
  • 我介绍的那种快速乘的方法是比较稳的。好写且不会掉精度,还跑得快。而且可以应对 long \\;long 范围的一切情况。

关于我们

北京某某塑料板材有限公司

启航娱乐环保设计研发站成立于1970年,公司专业提供环保设备研发设计,环保设备销售等启航注册,登录,...

在线咨询在线咨询
咨询热线 020-88888888


返回顶部

平台注册入口