位运算技巧

目录

零.一些组合位运算

一.快速幂

二.64位乘法取模

三.状态压缩

四.求一个数在二进制表示下为一的数位

五.成对变换

六.例题


零.一些组合位运算

操作运算
取出整数n在二进制表示下的第k位(n>>k)&1
取出整数n在二进制表示下的第0~k-1位(后k位)(n&((1<<k)-1)
把整数n在二进制表示下的第k位取反n xor (1<<k)
对整数n在二进制表示下的第k位赋值1n|(1<<k)
对整数n在二进制表示下的第k位赋值0n&(~(1<<k))


 

一.快速幂

求 a 的 b 次方对 p 取模的值。

输入格式

三个整数 a,b,p,在同一行用空格隔开。

输出格式

输出一个整数,表示a^b mod p的值。

数据范围

0≤a,b≤109
1≤p≤109

输入样例:

3 2 7

输出样例:

2

b&1取出b在二进制表示下的最低位,b>>1可以舍去最低位,两者结合,可以得到b在二进制表示下的所有数位。a^b其实相当于\coprod_{i=0}^{k-1}a^{c_{i}*2^{i}}(i表示b的二进制表示下的第i个数位,而a^{2^{i}}=({a^{2^{i-1}}})^{2}

其中k = log2(b+1)(向上取整),所以该算法的时间复杂度位O(log2(b))。

#include<bits/stdc++.h>
using namespace std;
int power(int a, int b , int p){
    int ans = 1 % p ;
    for( ; b ; b >>= 1) {
        if(b & 1) ans = (long long)ans * a % p;
        a = (long long)a * a % p;
    }
    return ans;
}
int main(){
    int a , b , p;
    cin >> a >> b >>p;
    int ans = power(a, b , p) % p;
    cout << ans << endl;
    return 0;
}

二.64位乘法取模

求 aa 乘 bb 对 pp 取模的值。

输入格式

第一行输入整数aa,第二行输入整数bb,第三行输入整数pp。

输出格式

输出一个整数,表示a*b mod p的值。

数据范围

1≤a,b,p≤1018

输入样例:

3
4
5

输出样例:

2

原理与快速幂相似。

#include<bits/stdc++.h>
using namespace std;
long long mul(long long a, long long b, long long p){
    long long ans = 0;
    for(; b;b >>= 1) {
        if(b & 1) ans = (ans + a) % p;
        a = a * 2 % p;
    }
    return ans;
}
int main(){
    long long a , b , p;
    cin >> a >> b >> p;
    long long ans = mul(a, b, p);
    cout << ans <<endl;
    return 0;
}

python做法(419ms)

a=input()
b=input()
p=input()
print((a*b)%p)

三.状态压缩

        二进制状态压缩:将一个长度为m的bool数组用一个m位二进制整数表示并存储的方法。

给定一张 n 个点的带权无向图,点从0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 ii 到 jj 的距离(记为 a[i,j]a[i,j])。

对于任意的 x,y,z,数据保证a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

数据范围

1≤n≤201≤n≤20
0≤a[i,j]≤1070≤a[i,j]≤107

输入样例:

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

输出样例:

18

        可以通过暴力的方法,枚举n个点的全排列,时间复杂度为O(n*n!),通过二进制状态压缩dp可以优化到O(n^{2}*2^{n})。

        我们可以使用一个n位二进制数表示在任意时刻有哪些点被经过,哪些点没有被经过。若其第i位为1,则表示第i个点已经被经过,反之未被经过。用F[i,j]表示“点被经过的状态”对应的二进制数为i,且目前处于点j时的最短路径。起点时,有f[1,0]=0,即只经过了点0(i只有第0位为1),目前出于起点0,最短路长度为0。由于要求最短路径,所以方便起见,将f内的其他数值设置为无穷大。目标状态为f[(1<<n)-1,n-1],即经过所有点(i的所有位都是1),处于终点n-1的最短路。

        在任意时刻有f[i,j] = f[ i^(1<<j),k] + w(k,j)] ,其中 0<= k < n&&((i >> j)&1) = 1,即当前时刻处在j点的状态,由未处于j点但其他路径相同的状态转移而来,所以i^(1<<j)找到未经过j点的状态(除了未经过j点,其他点的情况与i相同)然后k可能时i的二进制表示中任意一个除j外的数位为1的点。所以k要满足((i^1<<j)>>k&1)==1,即将第j位取反后,取出第k位,如果为1,则可以有f[i^(1<<j),k]转移到f[i,j]。

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

int f[1 << 20][20];

int Hamilton(int n , int w[20][20]){
    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)
            for(int k = 0; k < n; k++) if ((i^1<<j) >> k & 1)
                f[i][j] = min(f[i][j], f[i^1<<j][k]+w[k][j]);
    return f[(1<<n) - 1][n - 1];
}
int main(){
    int w[20][20], n;
    cin >> n;
    for(int i = 0; i < n; ++i)i
        for(int j = 0; j < n; ++j)
            scanf("%d", &w[i][j]);
    int ans = Hamilton(n , w);
    printf("%d",ans);
}

以下是一些模板和题目,不做详细讲解。 

四.求一个数在二进制表示下为一的数位

 模板,不做讲解。

#include<bits/stdc++.h>
using namespace std;
int Hash[37],n;
int main(){
	for (int i = 0; i < 36; i++) Hash[(1ll << i) % 37] = i;
	while(cin >> n , n){
		while(n > 0) {
			cout << Hash[(n & -n) % 37] << ' ';
			n -= n & -n;
		}
		cout << endl;
	}
	return 0; 
}

五.成对变换

对于非负整数n:

当 n 为偶数时,n xor 1 等于 n + 1;

当 n 为奇数时,n xor 1 等于 n - 1;

六.例题

        本题不难,建议自己想想怎么做。 

 AC代码

#include<bits/stdc++.h>
using namespace std;
long long pre[100];
int x,y,ans;
int BitCount(long long n)
{
    int c =0 ; 
    while (n >0)
    {
        if((n &1) ==1) 
            ++c ;
        n >>=1 ;
    }
    return c ;
}
void dfs(int p,long long sum,long long fsum,int f){
	if(p>15) return;
	if(sum>=fsum)ans=min(ans,BitCount(sum-fsum)+f);
	dfs(p+1,sum,fsum+pre[p],f+1);
	dfs(p+1,sum,fsum,f);
}
int main(){
	int t;
	cin>>t;
	pre[1]=1;
	for(int i=2;pre[i]<=1e13;i++){
		pre[i]=pre[i-1]*i;
	}
	while(t--){
		ans=INT_MAX;
		long long n;
		cin>>n;
		dfs(3,n,0,0);
		if(ans==INT_MAX) cout<<-1<<endl;
		else cout<<ans<<endl;
	}
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Krito.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值