一.二进制与十进制的转换
可以用短除法将十进制转换为二进制,将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取除得的余数,即换算为二进制数的结果。
,void DtoB(int n) { int B[32];//用来存储二进制数 int i=0;//用来记录位数 if(n==0)cout<<0;//特殊处理0,避免死循环 else{ while(n>0) { B[i++]=n%2;//余数存进数组里 n=n/2; } for(int j=i-1;j>=0;j--) { cout<<B[j];//倒序输出 } cout<<endl; } }
可以用按权展开加和求二进制转换为十进制,就是每一位的权值,一般等于 2^i-1,以十进制下的1000(千位数)来举例,第一位(个位)的位权是1,第二位为10,第三位为100,第四位为1000。而二进制转十进制则是先将位权展开与其对应的值相乘,最后再将这个值相加起来得到的则是对应的十进制表示
void BtoD(string Bin) { int power=0;//记录位数 int n=0;//储存十进制 for(int i=Bin.length()-1;i>=0;i--) { if(Bin[i]=='1')n+=pow(2,power); power++; } cout<<n<<endl; }
二.二进制的子集生成和组合问题
一个包含有n个元素的集合{a0,a1,a2,a3,...,an-1},它有子集{},{a0},{a1},...,{a0,a1,a2},...,{a0,a1,a2,...,an-1},供2^n-1个,用二进制的概念进行对照是最直观的,每个子集对应一个二进制数,这个二进制数中的每个1都对应着这个子集中的某个元素,而且子集中的元素是没有顺序的,下面程序通过处理每一个二进制数可以打印出所有子集
#include<bits/stdc++.h> using namespace std; void subset(int n) { for(int i=0;i<(1<<n);i++)//1左移n位再减一,对应相应位数的二进制数 { for(int j=0;j<n;j++) { if(i&1<<j)cout<<j<<' ';//按位与,如果是1,就打印出对应的位数 } cout<<endl; } } int main(int argc, char** argv) { int n; cin>>n; subset(n); return 0; }
三.位运算\快速幂
如何判断二进制数中1的个数为k?简单的方法是对这个n位二进制数逐位检查,另外有一个更快的方法,它可以直接定位二进制数中 1的位置,跳过中间的 0。它用到一个神奇的操作一kk=kk & (kk-1),功能是消除 k 的二进制数的最后一个 1。连续进行这个操作,每次消除一个1,直到全部消除为止,操作次数就是 1的个数。例如二进制数1011,经过连续 3 次操作后,所有的 1都消除了:
#include<bits/stdc++.h> using namespace std; void pset(int n,int k) { for(int i=0;i<(1<<n);i++) { int num=0,kk=i; while(kk) { kk=kk&(kk-1); num++; } if(k==num) { for(int j=0;j<n;j++) { if(i&(1<<j))cout<<j<<' '; } cout<<endl; } } } int main(int argc, char** argv) { int n,k; cin>>n>>k; pset(n,k); }
快速幂以及扩展的矩阵快速幂,由于应用场景比较常见,也是竞赛中常见的题型。幂运算 a^n即n个a相乘快速幂就是高效地算出a^n。当n很大时如 n^10计算a这样大的数 Java 也不能处理,一是数字太大,二是计算时间很长。下面先考虑如何缩短计算时间,如果用暴力的方法直接算 a^n,即逐个做乘法,复杂度是 O(n),即使能算出来,也会超时。很容易想到快速幂的办法: 先算 a^2,然后继续算平方(a^2)^2,一直算到 n次幂。这是分治法的思想,复杂度为 O(logen)。还有更好的方法就是用位运算做快速幂,下面是代码:
#include<bits/stdc++.h> using namespace std; void fastpow(int a,int n) { int base=a; int ans=1;//返回结果 while(n) { if(n&1)//如果n的最后一位是1,表示这个地方需要乘 { ans=ans*base; } base*=base; n>>=1;//把刚处理的最后一位去掉 } cout<<ans; } int main(int argc, char** argv) { int a,n; cin>>a>>n; fastpow(a,n); }
虽然时间复杂度很短,但是幂运算结果非常大,常常会超过变量类型的最大值,通常会要求做取模运算
if(n&1) { ans=(ans*base)%mod; } base=(base*base)%mod;
矩阵只有当左边矩阵的列数等于右边矩阵的行数时,它们才可以相乘,乘积矩阵的行数等于左边矩阵的行数,乘积矩阵的列数等于右边矩阵的列数
#include<bits/stdc++.h> using namespace std; const int INF=100; int m,n; struct Matrix{ int m[INF][INF]; Matrix(){ memset(m,0,sizeof(m));//初始化为0 } };//定义矩阵的结构体,方便运算 Matrix mm;//储存初始矩阵 Matrix mult(Matrix a,Matrix b) { Matrix c; for(int i=0;i<m;i++) { for(int j=0;j<m;j++) { for(int k=0;k<m;k++) { c.m[i][j]+=a.m[i][k]*b.m[k][j]; } } } return c; } Matrix fastpow(Matrix a,int n)//快速幂 { Matrix ans; for(int i=0;i<INF;i++) { ans.m[i][i]=1; } Matrix base=mm; while(n) { if(n&1) { ans=mult(ans,base); } base=mult(base,base); n>>=1; } return ans; } int main(int argc, char** argv) { cin>>m>>n;//m*m矩阵,n为幂数 for(int i=0;i<m;i++) { for(int j=0;j<m;j++) { cin>>mm.m[i][j]; } } Matrix ans=fastpow(mm,n) ; for(int i=0;i<m;i++) { for(int j=0;j<m;j++) { cout<<ans.m[i][j]<<' '; } cout<<endl; } }
四.利用二进制和位运算暴力枚举
二进制枚举利用位运算的高效性,遍历所有的二进制状态时,每次只需要进行位级别的操作。相比于传统的递归或循环枚举方法,二进制枚举的效率更高。二进制枚举通过位运算实现简洁高效的状态切换和组合生成,具有较强的可扩展性和适用性。在解决相应的问题时,使用二进制枚举可以提高代码的可读性和运行效率。
下面用一个例题具体运用二进制的暴力枚举(蓝桥杯省赛真题)
问题描述:话说大诗人李白,一生好饮。幸好他从不开车。一天,他提着酒壶,从家里出来,酒壶中有酒两斗。他边走边唱:
1. 无事街上走,提壶去打酒。 2. 逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店5次,遇到花10次,已知最后一次遇到的是花,他正好把酒喝光了。请你计算李白遇到店和花的次序,有多少种可能得方案。
可以用二进制枚举进行解题,将遇到店和花的次序抽象为一组15位二进制数,将遇到店记为1,遇到花记为0,并且最后一个数为0,最后剩下0斗酒,可以进行优化,即抽象成一组14位的二进制数,最后剩下1斗酒
#include<bits/stdc++.h> using namespace std; int main() { int num=0; int n=14;//利用位运算构建一组14位二进制数 for(int i=0;i<1<<n;i++)//从0遍历到11111111111111 { int alc=2,num_1=0,num_0=0;//初始化两斗酒 for(int j=n-1;j>=0;j--)//从最高位开始进行处理 { if(i&1<<j)//此位为1,表示遇到店,数量翻倍 { num_1++; alc*=2; } else{//此位为0,要减少一斗 num_0++; alc--; } } if(alc==1&&num_1==5&&num_0==9)num++;//判断条件条件成立 } cout<<num;//答案为14 }
李白打酒强化版(动态规划):
这一路上, 他一共遇到店 N 次, 遇到花 M 次。已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白这一路遇到店和花的顺序, 有多少种不同的可能?
注意: 壶里没酒 ( 0 斗) 时遇店是合法的, 加倍后还是没酒; 但是没酒时遇 花是不合法的。
1≤N,M≤100
#include <iostream> using namespace std; int n,m; long long dp[105][105][105];//i,j,k表示,遇到i次店,j次花,酒k斗,有多少种可能 int main(){ cin>>n>>m; //初始化 memset(dp,0,sizeof(dp)); dp[0][0][2]=1; for(int i=0;i<=n;i++){ for(int j=0;j<=m;j++){ for(int k=0;k<=m;k++){//为了能把酒喝完,k最多不能超过m。 if(k%2!=0){//k为奇数,说明从花来的 if(j>=1)dp[i][j][k]=dp[i][j-1][k+1];//该状态只有可能从其他一种状态得来,可以直接= } if(k%2==0){//k为偶数,从花或者店来的 if(j>=1)dp[i][j][k]+=dp[i][j-1][k+1];//+=是因为该状态可能从其他很多不同状态得来 if(i>=1)dp[i][j][k]+=dp[i-1][j][k/2];//判断i,j是否大于1,防止减一的时候越界 } dp[i][j][k]%=1000000007; } } } cout<<dp[n][m-1][1]; }