【算法竞赛进阶指南】基本算法:位运算

a^b POJ1995 位运算与快速幂

关于快速幂的基本知识:

  • 每一个正整数都可以唯一的表示为若干指数不重复的2的次幂的和。(奇数由-1的偶数+1得到)
  • b&1表示b的最低位(右边第一位);b>>=1可以舍去最低位。
  • a(b+1)=ab*a;

简单版

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=105;
int main()
{
	ll a,b,p;scanf("%lld%lld%lld",&a,&b,&p);
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%p;//若最后一位是1 就乘一次a
		b>>=1; //右移一位
		a=a*a%p;//a如从0次方变1次方 
	}
	printf("%lld",ans%p);
	return 0; 
}

原版

//#include<bits/stdc++.h>
#include<stdio.h>
typedef long long ll;
#define pb push_back
#define fi first
#define se second
const int N=105;
int main()
{
	ll t;scanf("%lld",&t);
	while(t--)
	{
		int m,h;
		scanf("%d%d",&m,&h);
		ll ans=0;
		while(h--)
		{
			ll a,b;scanf("%lld%lld",&a,&b);
			ll anst=1;
			while(b)
			{
				if(b&1) anst=anst*a%m;
				b>>=1;
				a=a*a%m;
			}
			anst%=m;
			ans+=anst;
			ans%=m;
		}
		printf("%lld\n",ans%m);
	}
	
	
	return 0; 
}

64位整数乘法

把乘法拆成慢慢加再mod,这样就不会在过程爆LL(每一步都不超过1018);
原题

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=105;
int main()
{
	ll a,b,p;scanf("%lld%lld%lld",&a,&b,&p);
	ll ans=0;	
	while(b)
	{
		if(b&1) ans=(ans+a)%p;	
		b>>=1;
		a*=2;
		a%=p;
	}
	printf("%lld",ans%p);
	return 0; 
}

二进制状态压缩: 最短Hamilton路径

原题
这是一个旅行商问题,无多项式解,算法复杂度很高。
用dp和二进制压缩。

关于二进制压缩和二进制表示状态:
我们以n为3举例,共有以下几种情况:
3个点,每一位表示这个点是否经过:

000  //每个点都没有经过
001  //只有0号点经过了
010  //只有1号点经过了
011  //....
100
101
110
111
000
001
010
011
100
101
110
111

于是,我们发现,如果只有0号点,1号点都经过了,那么我们一定会停在0或1号点上。

我们如果想知道到111这种情况的最短路,那么我们就要知道:
110的最短路和min(2点到0点距离,1点到0点距离);
101的最短路和min(2–>1距离,0—>1距离);
011的最短路和min(0—>2,1—>2);
有一点Floyd的思想,即i情况到j情况的最短路可以由i到k的最短+k到j的最短
结合上面的发现,我们得到:i到k的最短不能有j,k到j的最短必须有j。因此i到j可以由i到没有j的k + k到j;

也就是说,想要111,必须得到110,101,001.这三者是由111中删掉一位得到,即某一位取反操作得到。
j的第i位取反操作:

i^(1<<j)  //不加括号也行 但是这样好看一些啦

判断i的j不存在但是k存在的操作:返回1则存在

if((i^(1<<j))>>k&1)

本题代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1<<20,M=21;
int f[N][M],w[M][M];//f[i][j] 经过的状态为i二进制,到达了j 
int n;
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			scanf("%d",&w[i][j]);
		}
	}
	
	memset(f,0x3f,sizeof(f));
	f[1][0]=0; //只有一个点 且就在这个点上
	
	for(int i=1;i<(1<<n);i++) //枚举每一种状态
	{
		for(int j=0;j<n;j++)  //枚举每一种到的点
		{
			if(i>>j&1) //到j 所以第j位为1 
			{
				for(int k=0;k<n;k++) //枚举每种k的情况
				{
					if((i^(1<<j)>>k&1))//i的第j位不存在 但是k存在
					f[i][j]=min(f[i][j],f[i^(1<<j)][k]+w[k][j]); 
				 } 
			}
		}
	 } 
	 
	printf("%d",f[(1<<n)-1][n-1]);
	return 0; 
}

还可以看看y总的视频讲解:视频

起床困难综合症:贪心+二进制位运算

原题
测试的时候有个样例一直过不了,后来才知道ACwing如果不提交,有的输入输不进去…我竟然改了3个多小时…

关于本题:

  • 位运算的特点:二进制表示下不进位。
  • 1e9 < 230 所以可以从第30位枚举到第0位;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<string,ll> pii;
#define pb push_back
#define fi first
#define se second
const int N=2e5+10;
int n,m;
pii a[N];
int cal_bit(int bit,int now)
{
	for(int i=0;i<n;i++)
	{
		int x=(a[i].se>>bit)&1;
		string s=a[i].fi;
		if(s=="AND") now&=x;
		else if(s=="OR") now|=x;
		else if(s=="XOR") now^=x;
	}
	return now;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++) 
	{
		string s;ll t;
		cin>>s>>t;
		a[i].fi=s,a[i].se=t;
	}
	
	ll val=0,ans=0;
	
	for(int i=30;i>=0;i--)
	{
		int res0=cal_bit(i,0);
		int res1=cal_bit(i,1);
		
		if(m>>i)
		{
			if(res0>=res1) ans|=(res0<<i);
			else ans|=(res1<<i),m-=(1<<i);
		}
		else ans|=(res0<<i);
	}
	
	printf("%d",ans);	
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值