89. ab
求 a 的 b 次方对 p 取模的值。
输入格式
三个整数 a,b,p ,在同一行用空格隔开。
输出格式
输出一个整数,表示ab mod p的值。
数据范围
0≤a,b≤109
1≤p≤109
输入样例:
3 2 7
输出样例:
2
思路
因为b数据范围很大,不可能用爆搜解决。这里可以考虑用二进制表示b,从而减少运算
b=ck-12k-1+ck-22k-2+…+c020
`
两个底数相同的数相乘,指数相加,所以我们就是要用2的整次幂把指数 b 加出来。那么如果 b 的二进制表示里第 k 位是1,就加上 2k,也就是乘上 a2^^k,这就是快速幂的思想
(迭代版)
#include<iostream>
using namespace std;
const int N = 1e9 + 10;
typedef long long ll;
int a,b,p;
ll ans;
int main()
{
scanf("%d%d%d", &a, &b, &p);
ans = 1 % p; //可以处理p=1的情况
for(;b;b >>= 1)
{
//如果第k位是1,就乘上对应的 a^2^k;
if(b & 1 ) ans = ans * a % p; //b当前位是否为1,每次循环mod p是为了防止溢出
a = (long long )a * a % p;
//这里是倍增,每次求 a^2^1 %p,a^2^2 %p,a^2^3 %p,…a^2^1 %p,a^2^2 %p,a^2^3 %p,… 的值;
}
//两个底数相同的数相乘,指数相加,所以我们就是要用2的整次幂把指数 b 加出来。
//那么如果 b 的二进制表示里第 k 位是1,就加上 2^k,也就是乘上 a^2^k
/*
while写法
while(b)
{
if(b & 1) ans = ans * a % p ;
a = (long long ) a * a % p;
b >>= 1;
}
*/
cout << ans <<endl;
return 0;
}
(递归版)
#include<iostream>
using namespace std;
typedef unsigned long long ull;
ull quick_pow(ull a,ull b,ull p)
{
if(b==0) return 1%p;
a%=p;
ull res=quick_pow(a,b>>1,p);
if(b&1) return res*res%p*a%p;
return res*res%p;
}
int main()
{
int a,b,p;
cin.tie(0);
ios::sync_with_stdio(false);
cin>>a>>b>>p;
cout<<quick_pow(a,b,p)<<endl;
return 0;
}
tips:
- i>>k&1可以判断i的第k位是否为1
- 可以判断奇偶数,i & 1 = 1 >> 奇数
90. 64位整数乘法
求 a 乘 b 对 p 取模的值。
输入格式
第一行输入整数a,第二行输入整数b,第三行输入整数p。
输出格式
输出一个整数,表示a*b mod p的值。
数据范围
1≤a,b,p≤1018
输入样例:
3
4
5
输出样例:
2
思路
这题与上一题不同的是,变量abp都在1018级别,c++最高内置整数类型是64位,不存在一个128位的可供转换的整数类型,所以需要另一种方法解决。
- 方法一
类似于快速幂
a * 2i = (a*2i-1 2 若已求出 a * 2i-1 mod p,则计算 (a * 2i-1) * 2 mod p,则运算过程中每一步的结果都不超过21018,则可以通过k次递推求出每个乘积项。当ci等于1时,把该乘积项累积到答案即可。时间复杂度O(log2b)
#include <iostream>
using namespace std;
typedef unsigned long long ull;
ull a,b,p;
ull ans;
int main()
{
cin >> a >> b >> p;
ans = 0;
for(;b;b >>= 1)
{
if(b&1) ans = (ans + a ) % p; //如果是1,则把乘积项加上,mod p防溢出
a = a * 2 % p;
}
cout << ans << endl;
return 0;
}
801. 二进制中1的个数
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
数据范围
1≤n≤100000,
0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
原码 补码 反码
- 原码:是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值
- 举例:
1010 : 最高位为‘1’,表示这是一个负数,其他三位为‘010’,
即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示幂运算符)
所以1010表示十进制数(-2)
同理 0010表示十进制数(2)
0001+0010=0011 (1+2=3)ok
0000+1000=1000 (+0+(-0)=-0) ok
0001+1001=1010 (1+(-1)=-2)no
由此引出了反码的概念来解决1 + -1 = -2的问题
- 反码:正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反。
举例:
若以带符号位的四位二进制数为例:
3是正数,反码与原码相同,则可以表示为0011
-3的原码是1011,符号位保持不变,低三位(011)按位取反得(100)所以-3的反码为1100
0001(1 )+1110 (- 1)=1111(-7)no 正确答案为0
1110(-1)+1101(-2)=1011(-4)no 正确答案为-3
1110(-1)+1100(-3)=1010(-5)no 正确答案为-4
细心的你可能发现,除了第一个,其他计算错误的结果差值只是相差了1
由此引出了补码的概念来解决问题
- 补码:正数的补码等于他的原码, 负数的补码等于反码+1。也等于正数反码+1。
可能有小伙伴疑惑为什么 0001(1 )+1110 (- 1)=1111(-7)no 正确答案为0
如果这个+1不是会错误吗??
哈哈,显然不是
机器码的位数在操作系统中是固定位,假设只有4位
源码 1 : 0001 -1:1001
补码 1 :0001 -1:1110 + 1 = 1111
补码相加 1 + -1 = 0001 + 1111 = 10000,因为保留4位,所以最终结果为0000
nice~~完美
负数的补码等于反码+1,也等于正数反码+1。
1. -1 :1001 -> 1111
2. -1 : (1: 0001)的反码 1110 + 1 -> 1111
- lowbit原理
- 根据计算机负数表示的特点,如一个数字原码是10001000,他的负数表示形势是补码,就是反码+1,反码是01110111,加一则是01111000,二者按位与得到了1000,就是我们想要的lowbit操作