二进制与位运算(暴力枚举)

一.二进制与十进制的转换

可以用短除法将十进制转换为二进制,将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取除得的余数,即换算为二进制数的结果。

,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];
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值