0x01位运算——64位整数乘法

0x01 位运算——64位整数乘法

在这里插入图片描述

  • 问题简析

C++内置的最高整数类型是64位,所以显然不能通过直接运算得到答案,我们需要一些特殊的处理方法,在这里提供两种方法。

  1. 思路一

类似于快速幂的思想,把b拆分成k位二进制。
b = C k − 1 ∗ 2 k − 1 + C k − 2 ∗ 2 k − 2 . . . + C 0 ∗ 2 0 。 b=C_{k-1}*2^{k-1}+C_{k-2}*2^{k-2}...+C_{0}*2^{0}。 b=Ck12k1+Ck22k2...+C020
a ∗ b = C k − 1 ∗ 2 k − 1 ∗ a + C k − 2 ∗ 2 k − 2 ∗ a . . . + C 0 ∗ 2 0 ∗ a 。 a*b=C_{k-1}*2^{k-1}*a+C_{k-2}*2^{k-2}*a...+C_{0}*2^{0}*a。 ab=Ck12k1a+Ck22k2a...+C020a
若 算 出 a ∗ b i − 1 m o d   p , 则 计 算 ( a ∗ b i − 1 ) ∗ 2 m o d   p 的 过 程 中 不 会 超 过 2 ∗ 1 0 18 , l o n g   l o n g 可 以 胜 任 。 若算出a*b^{i-1}mod~p,则计算(a*b^{i-1})*2mod~p的过程中不会超过2*10^{18},long ~long可以胜任。 abi1mod p(abi1)2mod p21018long long

  • 时间复杂度分析

分 解 出 的 项 数 为 k = ⌈ l o g 2 ( b + 1 ) ⌉ 分解出的项数为k=\left \lceil log_{2}(b+1) \right \rceil k=log2(b+1)
时 间 复 杂 度 : O l o g 2 ( n ) 时间复杂度:O log_{2}(n) :Olog2(n)

  • 代码实现
#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long ull;

const int N = 1e5+10,M = N,INF = 0x3f3f3f3f,P = 998244353;


int main()
{
    std::ios::sync_with_stdio(false);
    ll a,b,p;
    std::cin>>a>>b>>p;
    ll res = 0;
    for(; b ; b >>= 1)
    {
        if(b&1)res = (res + a)%p;
        a = a * 2 % p;
    }
    std::cout<<res;
    return 0;
}
  1. 思路二

利 用 a ∗ b   m o d   p = a ∗ b − ⌊ a ∗ b / p ⌋ ∗ p , ⌊ ⌋ 为 向 下 取 整 利用a*b ~mod~p=a*b-\left \lfloor a*b/p \right \rfloor*p,\left \lfloor \right \rfloor为向下取整 ab mod p=abab/pp
首 先 , 假 设 a < p , b < p , 那 么 a ∗ b / p 下 取 整 之 后 一 定 也 小 于 p , 我 们 可 以 使 用 浮 点 数 完 成 中 间 计 算 。 首先,假设a<p,b<p,那么a*b/p下取整之后一定也小于p,我们可以使用浮点数完成中间计算。 a<p,b<p,ab/pp使
l o n g   d o u b l e 的 浮 点 数 有 效 数 字 在 18 long~double的浮点数有效数字在18 long double18~ 19 位 之 间 , 可 以 胜 任 。 19位之间,可以胜任。 19
a ∗ b 和 ⌊ a ∗ b / p ⌋ ∗ p 可 能 会 很 大 , 但 是 易 知 它 们 的 差 值 一 定 在 0 a*b 和\left \lfloor a*b/p \right \rfloor*p可能会很大,但是易知它们的差值一定在0 abab/pp,0~ p − 1 之 间 。 p-1之间。 p1
因 为 p 的 范 围 在 l o n g   l o n g 以 内 , 所 以 只 需 要 关 心 a ∗ b 和 ⌊ a ∗ b / p ⌋ ∗ p 在 l o n g   l o n g 范 围 内 的 位 , 更 高 的 位 一 定 是 相 同 的 。 因为p的范围在long~long以内,所以只需要关心a*b 和\left \lfloor a*b/p \right \rfloor*p在long~long范围内的位,更高的位一定是相同的。 plong longabab/pplong long

  • 时间复杂度分析

O ( 1 ) O(1) O(1)

  • 代码实现
#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long ull;

const int N = 1e5+10,M = N,INF = 0x3f3f3f3f,P = 998244353;


int main()
{
    std::ios::sync_with_stdio(false);
    ll a,b,p;
    std::cin>>a>>b>>p;
    a %= p,b %= p;
    ll c = (long double)a * b / p;
    ll res = a * b - c * p;
    if(res < 0)res += p;
    else if(res >= p)res -= p;
    std::cout<<res;
    return 0;
}
  • 注意点
  1. 为什么a和b要mod p?
    因为我们需要保证 a ∗ b / p a*b/p ab/p范围在long long内
  2. 为什么用别的浮点数类型(double)会错?
    因为double的有效位数过小,在乘p以后导致错误
    例子:0.002 * 10000和0.0018 * 10000显然不同
    精度足够以后:
    0.0018 * 10000 和0.001811 * 10000不会影响
  3. 为什么最后要有这一段代码?
if(res < 0)res += p;
else if(res >= p)res -= p;

因为在第一行对a和b mod p后,由模的性质可以知道最后答案可能会与原来的答案产生p的差值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值