一.移位运算
1.基础知识
1)左移:低位填充0,高位越界舍弃
2)右移(算数右移):高位填充符号位,低位截断
注意:右移为下取整,而整数/2为向零取整。例如:(-3)>>1 = -2;(-3)/2 = -1。
2.例题
1)a^b(快速幂)
算法原理:
根据十进制数到二进制数转换的规则,设
,其中
则
显然,上式的项数为从 到
,共
项,并且
:需要对a进行k-1次累叠平方
故当时,把a的累叠平方计入到答案中。
时间复杂度为。
注:代码中要防止越界。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define N 100
int power(int a,int b,int p){
int ans = 1;
// 执行k次,但最后一次的累叠平方不计入ans
for(;b;b>>=1){
if(b&1) ans = (ll)ans*a % p;
a = (ll)a*a % p;//对a进行累叠平方
}
return ans;
}
int main()
{
int a,b,p;
cin>>a>>b>>p;
cout<<power(a,b,p)<<endl;
return 0;
}
2)64位整数乘法
方法一:类似于快速幂的方法,时间复杂度为。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define N 100
ll mul(ll a,ll b,ll p){
// 执行k次,但最后一次的累叠乘2不计入ans
ll ans = 0;
for(;b;b>>=1){
if(b&1) ans = (ans+a) % p;
a = a*2%p;//
}
return ans;
}
int main()
{
ll a,b,p;
cin>>a>>b>>p;
cout<<mul(a,b,p)<<endl;
return 0;
}
方法二:令,利用:
时间复杂度为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define INF 0x3f3f3f3f
#define N 100
ull mul(unsigned long long a,unsigned long long b,unsigned long long p){
a%=p,b%=p;
ull c = (long double)a*b/p;//求整数部分
ull x = a*b,y =c*p;// ull自然溢出等价于对2^64取模
ull ans = (x-y+p)%p;
return ans;
}
int main()
{
unsigned long long a, b, p;
cin >> a >> b >> p;
cout << mul(a, b, p);
}
二.二进制状态压缩
1.基础知识:
二进制状态压缩是指:将一个长度指为m的bool数组用一个m位的二进制整数表示并存储。
操作 | 运算 |
---|---|
取出二进制的第k位 |
|
取出二进制的第0~k-1位 |
|
对二进制的第k位取反 |
|
对二进制的第k位赋值为1 |
|
对二进制的第k位赋值为0 |
|
2.例题:
1)起床困难症:https://www.acwing.com/problem/content/1000/
分析:
位运算的主要特点是在二进制下不进位,一个数的每一位是独立的,也就是说,ans的每一位只与最开始选定的x的每一位有关系,所以我们可以按ans从高位到低位依次枚举每一位置k(0<=k<30),依次考虑初始值x的相应位置是填0还是填1。
x的相应位置填1的要求:
1)保证x在范围
2)如果x相应位置填1比填0更优的话,则填1
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mkp make_pair
#define INF 0x3f3f3f3f
#define N 100005
int n,m;
pair<string,int> p[N];
int cal(int i,int init){
for(int j=0;j<n;j++){
int x = (p[j].second>>i)&1;
if(p[j].first=="AND") init &=x;
else if(p[j].first=="OR") init |=x;
else init ^=x;
}
return init;
}
// O(n*30)
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++){
string s;int x;
cin>>p[i].first>>p[i].second;
}
int init_sum = 0,ans = 0;//sum为初始值
// 从高位枚举ans的每一位
for(int i=29;i>=0;i--){
int res0 = cal(i,0);//填0
int res1 = cal(i,1);//填1
// 填1比填0更优
if(init_sum+(1<<i)<=m){
if(res0==1 && res1==1){
// init_sum += 0;(从贪心的角度,应该填0)
ans += (1<<i);
}
else if(res0==0 && res1==1){
init_sum += (1<<i);
ans += (1<<i);
}
else if(res0==1 && res1==0){
ans += (1<<i);
}
}
else{
ans += (res0<<i);
}
}
cout<<ans<<endl;
return 0;
}
三.成对变换
当n为偶数时(n&1==0),n^1 = n+1
当n为奇数时(n&1==1),n^1 = n-1
0<->1,2<->3等
四.lowbit运算
lowbit(n)定义为非负整数n在二进制下,从最低位的1及后边所有的0构成的数值。
,其中
为二进制从右向左第一个1所在的位置
int lowbit(n){
return n&-n;
}
lowbit运算配合hash可以找到整数二进制表示下所有是1的位置:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define N 100
int lowbit(int n){
return n&(-n);
}
int h[1<<20+5];//hash(lowbit(n)) = k位置;
int main()
{
int n;
for(int k=0;k<=20;k++) h[1<<k] = k;
int ans = 0;
while(cin>>n){
ans = 0;
while(n>0){
cout<<h[lowbit(n)]<<" ";
n -= lowbit(n);
ans++;
}
cout<<endl<<"总数:"<<ans<<endl;
}
return 0;
}
或者:
数学原理:互不相等,且恰好取遍整数1~36。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define N 100
int lowbit(int n){
return n&(-n);
}
int h[37];
int main()
{
int n;
for(int k=0;k<=35;k++) h[(1ll<<k)%37] = k;
int ans = 0;
while(cin>>n){
ans = 0;
while(n>0){
cout<<h[lowbit(n)%37]<<" ";
n -= lowbit(n);
ans++;
}
cout<<endl<<"总数:"<<ans<<endl;
}
return 0;
}