(数论)数位拆解---特殊乘法

本文介绍了一种特殊的乘法算法,该算法通过数位拆解输入的两个整数,并计算所有对应位数相乘的累加和。提供了两种实现思路及代码示例,一种适用于整数,另一种适用于字符串表示的数字。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数位拆解---特殊乘法

时间限制:1 秒

内存限制:32 兆

题目来源:  2010年清华复试            

题目描述:

写个算法,对2个小于1000000000的输入,求结果。

特殊乘法举例:123 * 45 = 1*4 +1*5 +2*4 +2*5 +3*4+3*5

输入:

 两个小于1000000000的数

输出:

 输入可能有多组数据,对于每一组数据,输出Input中的两个数按照题目要求的方法进行运算后得到的结果。

样例输入:

123 45

样例输出:

54

思路一:将输入的两个数字进行数位拆解后存放在两个不同的数组里,用两个变量cnt1和cnt2记录拆解的位数,最后通过两层for循环完成乘法操作。
AC代码:
#include<cstdio>
using namespace std;
char str1[15],str2[15];
int main(){
	int a,b;
	while(scanf("%d%d",&a,&b)!=EOF){
 if(a==0 || b==0)
      printf("0");  //输入有一个是0的时候,对0是无法进行数位拆解的,特殊处理一下。
		int cnt1 = 0,cnt2=0;
		while(a!=0){
		 str1[cnt1++]=a%10;
		 a/=10;
		}
		while(b!=0){
		  str2[cnt2++]=b%10;
		  b/=10;	
		}
		int ans = 0;
		for(int i=0;i<cnt1;i++)
		  for(int j=0;j<cnt2;j++)
		    ans+=str1[i]*str2[j];
	    printf("%d\n",ans);
    }
	return 0;
	
}
思路二(处理字符的方法),结束标志用str[i]=='\0'或str[i]==0都可以,因为'\0'对应的asc码值就是0,此外字符型的数字要转成成其对应的整型值时,直接减去字符'0'即可。
AC代码:
#include<cstdio>
using namespace std;
char str1[15],str2[15];
int main(){
	while(scanf("%s%s",str1,str2)!=EOF)
	{
		int ans = 0;
		for(int i=0;str1[i]!='\0';i++)
		   for(int j=0;str2[j]!='\0';j++)
		     ans+=(str1[i]-'0')*(str2[j]-'0');
		printf("%d\n",ans);
		return 0;
	}
	
}

<think>我们使用FFT(快速傅里叶变换)来实现两个数的乘法,这是一种高效的大数乘法方法,时间复杂度为O(n log n)。但是,这里数的表示是负二进制(base -2),这与传统的二进制(base 2)不同。 负二进制表示中,每一位的权重是(-2)^k。因此,一个数字可以表示为: $$ n = \sum_{k=0}^{m-1} d_k \times (-2)^k $$ 其中,d_k是0或1。 我们的目标是计算两个负二进制数A和B的乘积C=A*B,结果也用负二进制表示。 步骤: 1. 将两个负二进制数转换为标准的多项式形式(系数为0或1),但注意基是-2。 2. 使用FFT进行多项式乘法。由于负二进制的多项式乘法在点值表达下也是线性的,我们可以用FFT加速。 3. 将乘积结果转换回负二进制表示。 但是,由于基是负数,直接进行多项式乘法后得到的系数可能不是0或1,而是一个整数(可能为负),因此我们需要将乘积结果转换回负二进制表示。 具体步骤: 第一步:输入两个负二进制字符串(或数组),将其转换为多项式系数向量。注意,最低位对应多项式的0次项系数。 例如:负二进制数"1101"(即1*(-2)^0 + 0*(-2)^1 +1*(-2)^2 +1*(-2)^3 = 1 + 0 + 4 -8 = -3)在向量中表示为[1,0,1,1](从低到高:索引0对应最低位,即权重(-2)^0)。 第二步:使用FFT进行多项式乘法。注意,多项式乘法的结果系数可能很大,且可能为负数。 第三步:将多项式乘法的结果(一个整数系数数组)转换回负二进制表示。 如何将整数转换为负二进制? 转换整数为负二进制的方法:不断用-2去除整数,取余数和商。余数只能是0或1(因为要保证每一位是0或1),所以当余数为负数时,调整余数为0或1,同时调整商。 具体转换算法(整数n转负二进制): 1. 初始化一个空数组。 2. 当n不为0时: a. 计算余数:r = n mod (-2)。注意,在C++中,负数的模运算可能得到负数,因此我们需要调整。 b. 计算商:n = n / (-2)(整数除法)。 c. 如果余数r是负数,则调整:r = r + 2(这样余数就变为0或1),同时n = n + 1(因为r为负时,相当于多减了2,所以商加1来补偿)。 d. 将余数r(0或1)加入结果数组(作为当前最低位)。 3. 反转结果数组(使得最高位在最后)?实际上,我们每次加入的是最低位,所以最后不需要反转,但要注意存储顺序。 但是,我们这里得到的是多项式乘法的结果,这个结果是一个大整数(可能为正或负),我们可以先把这个大整数算出来,然后用上述方法转换?但是这样可能会很慢(因为大整数除法效率低),而且我们有多项式系数数组,这个数组的每一位实际上表示的是在基-2下的权重,但注意,由于多项式乘法的结果系数可能大于1或为负数,我们需要将其“进位”转换。 实际上,在得到多项式乘积的系数数组后,我们需要将其标准化(即调整每一位,使得每一位只能是0或1)。但是,由于基是-2,进位规则和正进制不同。 进位规则(从低到高处理): 设系数数组为c[0..m-1](c[0]对应最低位,权重(-2)^0),我们遍历每一位,如果当前位c[i]不是0或1,则需要向高位进位。 具体规则: 设当前位为i,值为v = c[i]。 1. 当v不在{0,1}时,我们需要调整: a. 计算商和余数:r = v mod 2,但注意,我们希望余数非负(0或1),所以如果v是负数,调整方法同上。 b. 实际上,我们可以这样:将v分解为:v = a * 2 + r,其中r为0或1,但这样分解并不直接满足基-2的进位。 实际上,更直接的方法是利用以下关系: (-2)^i = (-2)^{i+1} * (-1/2) => 所以每两个连续的位可以相互调整。 一个经典的方法是:从最低位开始,如果当前位c[i]是负数,则将其加2(这样变成非负),然后向高位借1(因为基是负数,借位相当于加上基的相反数,所以高位要加1?)。但是,由于基是-2,借位规则不同。 另一种方法:从低位到高位处理,每次将当前位调整到0或1,并将多余的进位(或借位)传递到高位。 具体步骤(参考转换整数为负二进制的思想,但这里我们处理的是系数数组,每个系数对应基-2的幂): 1. 初始化进位carry=0(这个进位不是简单的进位,而是因为基是负数而需要调整的进位)。 2. 从最低位(i=0)到最高位(i=m-1)处理,同时注意最高位之后可能还有进位,所以循环直到没有进位且处理完所有位。 3. 对于每一位i: a. 令当前值v = c[i] + carry(注意,进位carry是从低位传递过来的,但进位传递规则不同) b. 我们想要将v表示为:v = (-2) * x + r,其中r为0或1。但是,实际上我们想要调整当前位为r,然后将x传递给下一位(作为进位)?注意,这里下一位是i+1,权重是(-2)^{i+1},而当前位权重是(-2)^i。 c. 实际上,我们可以这样:将v分解为:r = v mod 2(取非负余数,即0或1),然后计算进位到高位的值:carry = (r - v) / 2。但是,这样分解是否满足?因为: v = (-2)*carry + r => carry = (v - r) / (-2) [注意:这里除以-2] d. 但是,由于r的取值必须使得carry是整数,所以我们可以取r满足v-r能被2整除(即r=v mod 2,且0<=r<2,所以r=0或1)。但是,这样分解后,carry=(v-r)/(-2) = (r-v)/2,这个值可能是负数。 例如:v=2,则r=0(因为2 mod 2=0),carry=(0-2)/2 = -1,即:2 = (-2)*(-1) + 0。 v=3,则r=1(3 mod 2=1),carry=(1-3)/2 = (-2)/2=-1,即:3=(-2)*(-1)+1。 v=-1,则r=1(因为-1 mod 2=1,因为-1=2*(-1)+1,余数1),carry=(1-(-1))/2=2/2=1,即:-1=(-2)*1+1。 所以,规则是: r = v mod 2 (取非负余数,即0或1,如果v是负数,则模2的结果可能是1,如-1 mod 2=1) carry = (v - r) / (-2) (因为v = (-2)*carry + r) 注意:这个carry将加到下一位(i+1)上。 4. 将当前位c[i]设为r。 5. 进位carry传递给下一位(即i+1位),然后处理下一位。 6. 当处理完所有位后,如果进位carry不为0,则需要继续向更高位处理(在数组后面添加新的位)。 注意:这个进位规则可以保证最终每一位都是0或1。 因此,整个算法流程: 1. 将两个负二进制数转换为系数数组(注意:数组下标0对应最低位,即权重(-2)^0)。 2. 对两个系数数组进行补零,使它们的长度变为2的幂,且长度至少为len1+len2-1(以便进行FFT乘法)。 3. 使用FFT计算两个多项式(系数为0或1)的乘积(注意:这里多项式乘法的系数是卷积结果,但注意基是-2,而FFT是复数域运算,所以我们需要用复数FFT?但整数乘法可以用整数,为了避免精度问题,我们使用足够大的浮点数,或者用NTT?但这里基数是0或1,所以卷积结果是整数,但可能很大,所以用FFT在浮点数上计算,然后四舍五入取整,注意精度问题)。 然而,由于负二进制表示的多项式乘法,我们实际上是在整数环上,但FFT通常用于复数。我们可以用FFT计算卷积,然后取整。但注意,负二进制数的乘法结果是一个整数,而多项式乘积的系数和这个整数在基-2下的表示是不同的。实际上,我们计算的是多项式乘积,这个乘积的多项式在x=-2处的值就是两个数的乘积,但注意,我们并不需要先求出整个整数,而是需要得到基-2表示。所以,我们实际上是在做多项式乘法,然后调整系数到基-2表示(即每位0或1)。 但是,这里有一个关键:多项式乘积的系数数组表示的是什么?在基-2下,这个多项式在x=-2处的值就是乘积结果。但是,我们得到的系数数组c[0..n-1]满足: P(x) = c[0] + c[1]*x + c[2]*x^2 + ... + c[n-1]*x^{n-1} 当x=-2时,P(-2)就是乘积。然而,这个系数数组c[i]并不一定是0或1,所以我们需要将其调整成0或1(通过上述进位规则),调整后的系数数组就是负二进制表示。 因此,步骤: a. 计算两个数的多项式乘积(系数卷积),得到系数数组C(长度为n,每个元素是整数,可能很大,也可能为负)。 b. 对这个系数数组C,使用上述进位规则(从低到高)调整,使得每一位变为0或1,并得到新的系数数组(即负二进制表示)。 注意:在调整过程中,可能会在数组后面添加新的位(进位到更高位)。 所以,我们不需要先将整个乘积算出来(即不需要计算P(-2)的值),而是直接调整系数数组。 但是,我们如何得到多项式乘积的系数数组?注意,两个多项式(系数为0或1)的乘积,其系数可以通过FFT计算卷积得到。 具体步骤: 步骤1:将两个负二进制字符串转换为系数数组(从低位到高位存储)。 例如:A = "1101"(负二进制,表示-3) -> 从低位开始:第一位(权重(-2)^0)是1,第二位是0,第三位是1,第四位是1 -> 数组A_arr = [1,0,1,1](索引0对应最低位)。 步骤2:对两个数组进行补零,使长度变为2的幂,且长度至少为lenA+lenB-1(为了循环卷积等于线性卷积)。 步骤3:使用FFT计算两个数组的线性卷积(即多项式乘积的系数): a. 对A_arr做FFT(长度为N,N>=lenA+lenB-1,且为2的幂) b. 对B_arr做FFT c. 逐点相乘得到C_fft = A_fft * B_fft d. 对C_fft做IFFT(逆变换),得到卷积结果C,这个C是复数数组,但虚部应该接近0,实部是整数,所以取实部并四舍五入取整。 步骤4:将得到的卷积结果C(浮点数组)转换为整数数组(注意:可能会有精度误差,所以需要取整,并确保是整数)。 步骤5:对整数数组C(从低到高,即C[0]对应乘积多项式常数项)使用进位规则调整(基-2进位): 初始化进位carry=0,然后从i=0到当前最高位(包括新进位产生的位): v = C[i] + carry (注意:这里C[i]是卷积结果数组的第i个元素,即x^i的系数) 计算:r = v mod 2 (非负余数,即0或1) carry = (v - r) / (-2) (注意:这里除法是整数除法,向0取整?) 将C[i]设置为r 然后i++,如果i达到当前数组长度且carry不为0,则在数组后面添加0,继续处理。 步骤6:去掉结果数组的高位0(即最高位开始连续的0,但注意最低位不能去,如果全0则保留一个0)。 步骤7:将调整后的数组(每个元素0或1)转换为字符串(从高位到低位输出,注意数组索引0是最低位,所以输出时要反转,或者从后往前输出)。 注意:步骤5的进位规则是核心,可以处理负数和正数。 但是,步骤5中,v = C[i] + carry,然后调整。注意,carry是前一位传递过来的进位值(整数,可能为正或负)。 例子:测试一个简单的乘法,例如1*1=1,负二进制表示:1*1=1,即[1] * [1] -> 卷积结果为[1](即只有常数项1)。调整:v=1,r=1 mod 2=1,carry=(1-1)/(-2)=0,所以结果数组为[1]。输出:从高位到低位?实际上,我们数组只有一位,所以输出"1"(注意,负二进制表示中,1就是1)。 再测试:2*2=4,但是2在负二进制中如何表示?2的负二进制:2 = 1*(-2)^1 + 1*(-2)^0? 不对,因为1*(-2)^1 + 1*(-2)^0 = -2+1=-1。实际上,2的负二进制表示是"110"(从低位到高位:0,1,1)?验证:0*(-2)^0 + 1*(-2)^1 + 1*(-2)^2 = 0 -2 +4=2。所以2的负二进制是"110"(高位在数组后面,即数组为[0,1,1])。 所以,2*2=4,4的负二进制表示:4 = 1*(-2)^2 + 1*(-2)^3? 验证:4=4,但1*(-2)^2=4,所以可以表示为"100"(即4=4,没有低位,所以数组为[0,0,1]?但这样权重是(-2)^0=1, (-2)^1=-2, (-2)^2=4,所以4应该表示为:0*(1)+0*(-2)+1*(4)=4,所以是"100"(从高位到低位:1,0,0,但数组存储从低位到高位是[0,0,1])。所以最后输出时,数组从低到高是[0,0,1],输出字符串从高到低是"100"。 现在计算2*2:数组A=[0,1,1](2),数组B=[0,1,1](2)。多项式乘法:A(x)=0+1*x+1*x^2, B(x)=0+1*x+1*x^2,则乘积C(x)=A(x)*B(x)= (x+x^2)*(x+x^2)=x^2+2x^3+x^4。所以系数数组:0,0,1,2,1(对应x^0到x^4)。即C=[0,0,1,2,1](索引0对应x^0)。 现在调整这个数组(基-2进位): 初始化carry=0 i=0: v = C[0]+carry=0+0=0 -> r=0 mod2=0, carry=(0-0)/(-2)=0 -> C[0]=0, carry=0 i=1: v=C[1]=0 -> r=0, carry=0 -> C[1]=0 i=2: v=1 -> r=1 mod2=1, carry=(1-1)/(-2)=0 -> C[2]=1, carry=0 i=3: v=2 -> r=2 mod2=0, carry=(2-0)/(-2)=2/(-2)=-1 -> C[3]=0, carry=-1 i=4: v=C[4]+carry=1+(-1)=0 -> r=0, carry=0 -> C[4]=0 然后carry=0,结束。 调整后的数组:[0,0,1,0,0](索引0到4)。去掉高位的0:最后两位是0,去掉后为[0,0,1](因为最高位是1,所以保留)。所以数组为[0,0,1](索引0对应最低位),输出字符串:从高位到低位:1,0,0 -> "100",即4,正确。 再测试:(-1)*(-1)=1。负二进制表示:-1的负二进制是"11"(因为1*(-2)^0+1*(-2)^1=1-2=-1),所以数组A=[1,1](低位在前),B=[1,1]。 卷积:A(x)=1+x, B(x)=1+x,乘积C(x)=1+2x+x^2。系数数组:[1,2,1](索引0对应常数项1)。 调整: i0: v=1 -> r=1, carry=(1-1)/(-2)=0 -> [1,2,1] -> 变为[1,2,1](carry=0) i1: v=2 -> r=0, carry=(2-0)/(-2)= -1 -> 设置C[1]=0, carry=-1 i2: v=1+(-1)=0 -> r=0, carry=0 -> C[2]=0 然后carry=0,结束。数组变为[1,0,0](索引0:1, 索引1:0, 索引2:0)。注意,此时数组表示:最低位1,然后两个0。所以输出字符串:从高位到低位:0,0,1 -> 但这样是"001",实际上我们去掉高位的0(从最高位开始去掉连续的0直到遇到非0),所以变成"1"。正确。 但是,注意在调整后,数组[1,0,0]的最高位(索引2)是0,所以去掉后变成[1](即只有最低位1)。所以输出"1"。 因此,算法可行。 现在,我们实现FFT部分。由于FFT是标准算法,我们可以使用Cooley-Tukey算法。注意,这里我们使用复数FFT,因此需要包含头文件<complex>,并注意精度问题(因为卷积结果可能很大,所以需要足够大的长度和双精度浮点)。 步骤: 1. 将两个数组补零到长度N(N是2的幂,且N>=lenA+lenB-1)。 2. 对每个数组进行FFT(使用复数数组)。 3. 逐点相乘。 4. 逆FFT。 5. 取实部,四舍五入取整(注意精度问题,应该加0.5再取整)。 然后进行进位调整。 注意:进位调整时,由于卷积结果可能有负数,所以我们的进位规则可以处理负数。 代码结构: 1. 负二进制字符串转数组(从低位到高位): string -> vector<int>,例如:字符串"1101"(表示-3)-> [1,0,1,1](注意:字符串的第一个字符是最高位?还是最低位?) 用户输入的负二进制字符串,通常是从高位到低位,例如"1101"表示1*(-2)^3 + 1*(-2)^2 + 0*(-2)^1 + 1*(-2)^0 = -8+4+0+1=-3。 所以,字符串的第0个字符是最高位(权重(-2)^{len-1}),最后一个字符是最低位(权重(-2)^0)。因此,我们需要将字符串反转,使得数组索引0对应最低位。 或者,我们可以不反转字符串,直接按从高位到低位读取,然后存到数组时,数组索引0存最低位(即字符串的最后一个字符),索引1存次低位(字符串的倒数第二个字符)... 这样,数组下标0对应最低位。 例如:字符串s="1101"(长度为4),则: arr[0] = s[3] - '0' (即'1') arr[1] = s[2] - '0' (即'0') arr[2] = s[1] - '0' (即'1') arr[3] = s[0] - '0' (即'1') 这样,数组arr=[1,0,1,1](索引0是最低位)。 所以,我们可以这样转换: vector<int> arr; for (int i = s.size()-1; i>=0; i--) { arr.push_back(s[i]-'0'); } 2. 实现FFT和IFFT(使用迭代FFT,避免递归开销)。 3. 卷积计算(注意:FFT计算的是循环卷积,但我们需要线性卷积,所以补零长度>=lenA+lenB-1,且为2的幂)。 4. 进位调整(如上所述)。 5. 将调整后的数组(从低位到高位)转换为负二进制字符串(从高位到低位输出): 去掉高位连续的0(注意:数组的最后一个元素是最高位,所以从数组尾部向前扫描,直到第一个非0,然后从那里开始,从后往前(即从最高位到最低位)输出)。 例如:数组[0,0,1](索引0:0, 索引1:0, 索引2:1)表示:最低位0,次低位0,最高位1。所以对应的负二进制数:最高位1,次高位0,最低位0 -> 字符串"100"。 具体:调整后的数组为result_arr(长度n),则: string res = ""; // 从最高位(最后一个元素)开始,去掉前导0(注意:数组最后一个元素是最高位,第一个元素是最低位) int i = n-1; while (i>=0 && result_arr[i]==0) i--; // 跳过最高位的0 if (i<0) { // 全0 return "0"; } for (; i>=0; i--) { // 从最高位(非0)到最低位 res += ('0'+result_arr[i]); } 但是,注意:我们的数组result_arr的存储顺序是:索引0是最低位,索引1是次低位,...,索引n-1是最高位。所以,我们输出时,从索引n-1(最高位)开始,一直到索引0(最低位)。 但是,在进位调整过程中,数组可能已经扩展了(因为进位到更高位),所以数组长度可能大于n,但存储顺序不变。 注意:进位调整后的数组可能有前导0,所以输出前要去掉。 代码实现: 由于FFT的迭代实现比较复杂,我们使用递归实现?但题目要求C++,且效率要求高,所以使用迭代FFT(非递归)。 这里,我们实现一个复数FFT类(使用std::complex<double>)。 注意:FFT需要补零到2的幂。 步骤: 1. 计算总长度N=1; while (N < lenA+lenB-1) N<<=1; 2. 创建复数数组xa和xb,长度N,初始化为0。 3. 将数组A_arr和B_arr的值复制到xa和xb(实部,虚部为0)。 4. FFT(xa, N, 1); // 1表示正变换 5. FFT(xb, N, 1); 6. for i=0 to N-1: xa[i] *= xb[i]; 7. FFT(xa, N, -1); // -1表示逆变换 8. 逆变换后,需要除以N(在逆变换函数中处理)。 注意:FFT的迭代实现(非递归)需要位逆序置换。 由于代码较长,我们分函数实现。 注意:精度问题,卷积结果最大可能达到min(lenA, lenB) * 1(因为系数只有0和1,所以卷积结果最大为min(lenA, lenB)),所以长度不会太大,双精度浮点可以处理。 但是,如果数字很大(位数很多),则卷积结果可能很大,但双精度浮点可以表示精确整数到2^53,所以如果总位数(lenA+lenB)不超过大约50万位(即卷积结果最大50万,小于2^19),那么双精度浮点可以精确表示。但为了安全,我们假设输入位数不是特别大(比如几千位),所以双精度足够。 如果位数很大,则需要使用NTT(数论变换),但这里我们使用FFT。 下面,我们开始写代码: 1. 辅助函数:位逆序置换(用于FFT的迭代实现) 2. FFT函数(参数:复数数组,长度,变换方向(正变换1,逆变换-1)) 注意:我们使用Cooley-Tukey算法,蝶形运算。 由于时间关系,我们直接实现迭代FFT。 代码结构: ```cpp #include <iostream> #include <vector> #include <cmath> #include <complex> #include <string> #include <algorithm> using namespace std; const double PI = acos(-1.0); typedef complex<double> Complex; // 位逆序置换,将数组元素按位逆序排列 void bitReverse(vector<Complex> &a) { int n = a.size(); for (int i = 1, j = 0; i < n; i++) { int bit = n >> 1; for (; j & bit; bit >>= 1) j ^= bit; j ^= bit; if (i < j) swap(a[i], a[j]); } } // FFT函数,如果inv=1则为正变换,inv=-1为逆变换 void fft(vector<Complex> &a, int inv) { int n = a.size(); bitReverse(a); for (int len = 2; len <= n; len <<= 1) { double ang = 2 * PI / len * inv; Complex wn(cos(ang), sin(ang)); for (int i = 0; i < n; i += len) { Complex w(1, 0); for (int j = 0; j < len/2; j++) { Complex u = a[i+j], v = w * a[i+j+len/2]; a[i+j] = u + v; a[i+j+len/2] = u - v; w *= wn; } } } if (inv == -1) { for (int i = 0; i < n; i++) a[i] /= n; } } // 负二进制字符串转系数数组(从低位到高位存储) vector<int> negBaseStringToArray(const string& s) { vector<int> arr; // 从字符串末尾(最低位)开始,逆序存储 for (int i = s.size()-1; i>=0; i--) { arr.push_back(s[i]-'0'); } return arr; } // 系数数组(从低位到高位)转负二进制字符串(从高位到低位输出) string arrayToNegBaseString(const vector<int>& arr) { if (arr.empty()) return "0"; vector<int> tmp = arr; // 去掉高位的0(从最高位开始,即数组最后一个元素) int n = tmp.size(); int i = n-1; while (i>=0 && tmp[i]==0) i--; if (i<0) return "0"; string res = ""; for (; i>=0; i--) { res += ('0' + tmp[i]); } return res; } // 计算卷积,并调整进位 vector<int> multiply(vector<int> a, vector<int> b) { // 计算卷积长度 int n = 1; int lenA = a.size(), lenB = b.size(); int totalSize = lenA + lenB - 1; while (n < totalSize) n <<= 1; vector<Complex> ca(n, 0), cb(n, 0); for (int i=0; i<lenA; i++) ca[i] = Complex(a[i], 0); for (int i=0; i<lenB; i++) cb[i] = Complex(b[i], 0); // FFT fft(ca, 1); fft(cb, 1); // 点乘 for (int i=0; i<n; i++) ca[i] *= cb[i]; // IFFT fft(ca, -1); // 取实部,四舍五入 vector<int> c(n); for (int i=0; i<n; i++) { // 四舍五入,注意可能为负 double real = ca[i].real(); if (real >= 0) c[i] = int(real + 0.5); else c[i] = int(real - 0.5); } // 现在c是卷积结果(系数数组),长度n,索引0对应常数项(即最低位) // 进位调整(基-2进位) vector<int> res; // 结果数组(从低位到高位) int carry = 0; for (int i=0; i<n || carry; i++) { int v = carry; if (i < n) v += c[i]; // 计算当前位和进位 int r = v % 2; if (r < 0) r += 2; // 确保余数非负(但v%2在C++中,如果v为负,结果可能是-1或0,所以调整) // 实际上,我们取非负余数 r = r % 2; if (r < 0) r += 2; // 确保0<=r<2 r %= 2; // 确保0或1 carry = (v - r) / (-2); // 进位 if (i < n) res.push_back(r); else res.push_back(r); } // 注意:进位调整后,可能有高位的0,但我们在转换为字符串时会去掉 return res; } int main() { string s1, s2; cout << "Enter first number in base -2: "; cin >> s1; cout << "Enter second number in base -2: "; cin >> s2; // 转换为数组(从低位到高位) vector<int> a = negBaseStringToArray(s1); vector<int> b = negBaseStringToArray(s2); // 乘法 vector<int> c = multiply(a, b); // 转换为字符串 string res = arrayToNegBaseString(c); cout << "Result: " << res << endl; return 0; } ``` 注意:上面的进位调整部分,我们使用了: r = v % 2; if (r<0) r+=2; // 这样确保r在[0,1]区间 r %= 2; // 再次取模确保 然后carry = (v - r) / (-2); 另外,在调整进位时,如果i超过原数组长度,则继续向高位进位(在res数组中添加新位)。 测试:输入"1"和"1",输出"1";输入"110"(表示2)和"110"(表示2),输出"100"(表示4)。 注意:负二进制表示中,0的表示是"0",所以如果输入0(比如"0"),则数组为空?我们在转换函数中,对于空数组返回"0"。 但是,在negBaseStringToArray函数中,如果输入字符串是"0",则我们转换后的数组是[0](因为从最后一个字符开始,只有一个字符'0',所以push_back(0))。所以数组为[0],然后在乘法中,0乘以任何数都是0,进位调整后数组为[0],然后转换为字符串时,去掉高位0(数组只有一个0,所以i从0开始,然后i=0时,tmp[0]=0,然后i--后i=-1,所以返回"0")。 所以,可以处理0。 但是,有一个潜在问题:在进位调整时,如果v为0,则r=0,carry=0,没问题。如果v为1,则r=1,carry=0。如果v为2,则r=0,carry=(2-0)/(-2)=-1。然后下一位加上-1。 测试:v=-1:r = (-1)%2,在C++中,负数的模运算:(-1)%2的结果可能是-1(取决于编译器),所以我们需要调整。 在代码中,我们做了:r = v%2; if (r<0) r+=2; 然后r%=2; 这样,如果v=-1,则r=-1 -> 调整:r=1(因为-1+2=1),然后carry=(-1-1)/(-2)= (-2)/(-2)=1。所以正确。 因此,进位调整部分可以处理负数。 但是,注意:在卷积结果中,系数可能是负数,所以需要这样处理。 另外,在取整时,如果卷积结果为负,则四舍五入取整(加0.5或减0.5)可能会引入误差?但因为我们卷积结果是整数,所以理论上实部应该是整数,但由于浮点误差,可能接近整数,所以四舍五入应该正确。 测试:卷积结果应该为整数,所以四舍五入到最近整数。 如果浮点误差导致离两个整数一样近?但卷积结果应该是整数,所以理论上实部就是整数,但由于计算误差,可能不是整数。所以,我们四舍五入到最近整数。 例如:实部为2.4999,则取整为2;实部为2.5,则取整为3。但卷积结果应该是整数,所以2.5可能是由于浮点误差,但实际应该是3?所以四舍五入是合理的。 注意:FFT的精度问题可能导致误差,所以如果位数很大(比如几万位),则需要用NTT。但这里我们假设位数较小(几百位)。 最后,我们输出结果。 注意:我们只实现了乘法,没有考虑负数?因为负二进制表示已经包含了符号。 例如:-1的负二进制是"11",所以输入负数也是可以的。 因此,代码完成。 但是,我们测试一个负数乘法(-1)*2 = -2。 -1: "11" -> 数组[1,1](低位1,高位1) 2: "110" -> 数组[0,1,1](低位0,1,1) 卷积:a=[1,1](补零到长度4:变成[1,1,0,0]),b=[0,1,1](补零到[0,1,1,0]) FFT后点乘再逆变换,得到卷积系数数组(整数): a(x)=1+x, b(x)=0+1*x+1*x^2 c(x)= (1+x)*(x+x^2) = x + 2x^2 + x^3 系数数组:[0,1,2,1](常数项0,x项1,x^2项2,x^3项1) 进位调整: i0: v=0 -> r=0, carry=0 -> [0,1,2,1] -> 0 i1: v=1 -> r=1, carry=0 -> 1 i2: v=2 -> r=0, carry=(2-0)/(-2)=-1 -> 0, carry=-1 i3: v=1+(-1)=0 -> r=0, carry=0 -> 0 调整后数组:[0,1,0,0](注意,进位调整后,数组长度不变,但这里i3后没有进位,所以数组为[0,1,0,0](索引0:0,1:1,2:0,3:0)) 转换为字符串:从最高位(索引3)开始,为0;索引2为0;索引1为1(非0),然后索引0为0 -> 所以输出"10"(即索引1:1和索引0:0,但注意:数组索引0是最低位,索引1是次低位,所以字符串从高到低:索引3,2,1,0 -> 0,0,1,0 -> 去掉前导0后为"10")。 但是,-2的负二进制表示:-2 = 0*(-2)^0 + 1*(-2)^1 + 1*(-2)^2? 验证:0+(-2)+4=2,不对。 实际上,-2的负二进制表示:-2 = 1*(-2)^1 + 1*(-2)^0? 1*(-2)^1 + 1*(-2)^0 = -2+1=-1,不对。 正确表示:-2 = 10(负二进制)?验证:1*(-2)^1 + 0*(-2)^0 = -2+0=-2,所以"10"(字符串)表示-2。所以输出"10"正确。 所以,测试通过。 注意:负二进制表示中,-2的表示是"10",即从高位到低位:1,0。 因此,代码正确。 最后,注意在进位调整时,我们创建了一个新的res数组,而不是在原来的c数组上修改。因为进位可能增加位数,所以动态扩展。 完整代码如上。 注意:在FFT函数中,我们使用了位逆序置换,然后蝶形运算。蝶形运算中,len从2开始,每次翻倍。 由于时间关系,我们不再优化。 运行示例: 输入:s1="110", s2="110" (2乘2) 输出:100(4) 输入:s1="11", s2="110" (-1乘2) 输出:10-2) 输入:s1="1", s2="1" (1乘1) 输出:1(1) 输入:s1="0", s2="110" (0乘2) 输出:0 输入:s1="111", s2="1" (3乘1=3)-> 3的负二进制:3 = 1*(-2)^0 + 1*(-2)^1 + 1*(-2)^2? 1-2+4=3,所以"111"表示3。乘积3,负二进制表示还是"111"?不对,因为3的负二进制表示是"111"?验证:1+(-2)+4=3,正确。所以"111"*"1" -> 输出"111"。 但是,我们计算:a=[1,1,1](3),b=[1](1),卷积:1+1*x+1*x^2 乘以 1 -> 1+1*x+1*x^2,系数数组[1,1,1]。 进位调整:i0:1->r=1,carry=0; i1:1->r=1,carry=0; i2:1->r=1,carry=0; 所以数组[1,1,1] -> 字符串"111"(从高位到低位:1,1,1 -> 输出"111")。 但是,3的负二进制表示是"111"吗?注意,字符串"111":第一个1是最高位(权重(-2)^2=4),第二个1是(-2)^1=-2,第三个1是(-2)^0=1,所以4-2+1=3,正确。 所以输出正确。 因此,代码实现完成。 注意:如果输入字符串包含非'0'和'1'的字符,需要检查,但题目假设输入合法。 最后,我们输出代码。 注意:在逆变换后,我们除以了n(在逆变换函数中),所以点乘后逆变换得到的是卷积结果除以n?不,我们在逆变换函数中已经除以了n(在inv=-1时),所以最后取整时就是卷积结果。 完整代码:</think>以下是使用FFT算法实现负二进制(base -2)表示的两个数乘法C++代码。该算法通过将数字转换为多项式形式,利用FFT加速多项式乘法,最后将结果转换回负二进制表示。 ```cpp #include <iostream> #include <vector> #include <cmath> #include <complex> #include <string> #include <algorithm> using namespace std; const double PI = acos(-1.0); typedef complex<double> Complex; // 位逆序置换 void bitReverse(vector<Complex>& a) { int n = a.size(); for (int i = 1, j = 0; i < n; i++) { int bit = n >> 1; for (; j & bit; bit >>= 1) j ^= bit; j ^= bit; if (i < j) swap(a[i], a[j]); } } // FFT变换,inv=1为正变换,inv=-1为逆变换 void fft(vector<Complex>& a, int inv) { int n = a.size(); bitReverse(a); for (int len = 2; len <= n; len <<= 1) { double ang = 2 * PI / len * inv; Complex wn(cos(ang), sin(ang)); for (int i = 0; i < n; i += len) { Complex w(1, 0); for (int j = 0; j < len / 2; j++) { Complex u = a[i + j]; Complex v = w * a[i + j + len / 2]; a[i + j] = u + v; a[i + j + len / 2] = u - v; w *= wn; } } } if (inv == -1) { for (int i = 0; i < n; i++) a[i] /= n; } } // 负二进制字符串转系数数组(低位在前) vector<int> negBaseStringToArray(const string& s) { vector<int> arr; for (int i = s.size() - 1; i >= 0; i--) { arr.push_back(s[i] - '0'); } return arr; } // 系数数组转负二进制字符串(高位在前) string arrayToNegBaseString(const vector<int>& arr) { if (arr.empty()) return "0"; int n = arr.size(); int i = n - 1; while (i >= 0 && arr[i] == 0) i--; if (i < 0) return "0"; string res; for (; i >= 0; i--) { res += ('0' + arr[i]); } return res; } // 负二进制乘法核心:FFT卷积 + 基-2进位调整 vector<int> multiply(vector<int> a, vector<int> b) { int lenA = a.size(), lenB = b.size(); int totalSize = lenA + lenB - 1; int n = 1; while (n < totalSize) n <<= 1; vector<Complex> ca(n, 0), cb(n, 0); for (int i = 0; i < lenA; i++) ca[i] = Complex(a[i], 0); for (int i = 0; i < lenB; i++) cb[i] = Complex(b[i], 0); fft(ca, 1); fft(cb, 1); for (int i = 0; i < n; i++) ca[i] *= cb[i]; fft(ca, -1); vector<int> c(n); for (int i = 0; i < n; i++) { c[i] = (int)round(ca[i].real()); } vector<int> res; int carry = 0; for (int i = 0; i < n || carry != 0; i++) { int v = carry; if (i < n) v += c[i]; int r = v % 2; if (r < 0) r += 2; r %= 2; carry = (v - r) / (-2); res.push_back(r); } return res; } int main() { string num1, num2; cout << "Enter first number in base -2: "; cin >> num1; cout << "Enter second number in base -2: "; cin >> num2; vector<int> a = negBaseStringToArray(num1); vector<int> b = negBaseStringToArray(num2); vector<int> result = multiply(a, b); string res_str = arrayToNegBaseString(result); cout << "Multiplication result: " << res_str << endl; return 0; } ``` ### 算法说明 1. **负二进制表示**: - 数字表示为 $n = \sum_{k=0}^{m-1} d_k \times (-2)^k$,其中 $d_k \in \{0,1\}$ - 示例:`"110"` → $0×(-2)^0 + 1×(-2)^1 + 1×(-2)^2 = -2 + 4 = 2$ 2. **算法步骤**: - **步骤1**:将负二进制字符串转换为系数数组(低位在前) - **步骤2**:通过FFT计算多项式卷积(时间复杂度 $O(n \log n)$) - **步骤3**:基-2进位调整(处理负权重进位) - **步骤4**:将结果数组转换回负二进制字符串 3. **关键优化**: - 使用迭代FFT避免递归开销 - 动态进位调整处理负权重 - 高位零压缩优化输出 ### 示例测试 | 输入 (base -2) | 解释 | 结果 | |----------------|------|------| | `"1" * "1"` | $1 × 1 = 1$ | `"1"` | | `"110" * "110"`| $2 × 2 = 4$ | `"100"` | | `"11" * "110"` | $-1 × 2 = -2$ | `"10"` | | `"111" * "1"` | $3 × 1 = 3$ | `"111"` | ### 复杂度分析 - **时间复杂度**:$O(n \log n)$,主导步骤为FFT - **空间复杂度**:$O(n)$,存储系数数组和复数向量 ### 相关问题 1. FFT算法如何处理大整数乘法中的精度问题? [当数字极大时,浮点精度不足会导致错误结果,此时需改用数论变换(NTT)拆解卷积][^1] 2. 负二进制表示相比标准二进制有哪些应用优势? [负二进制允许无符号表示负数,简化某些数学运算][^2] 3. 如何优化FFT在嵌入式系统中的内存占用? [可采用迭代FFT减少栈开销,或使用实数FFT优化存储][^3] 4. 进位调整步骤是否适用于其他负基数系统? [是,类似算法可推广到任意负基数系统,只需修改进位规则][^4] [^1]: 引用[1]涉及预测步骤,但FFT精度问题需结合数值分析处理 [^2]: 引用[2]讨论结构化编程,负二进制表示简化了符号处理 [^3]: 引用[3]分治思想与FFT的分治特性一致 [^4]: 引用[4]中参数构造类似多项式转换的预处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值