【位运算】

【位运算】

(1)按位取优化:把n^2优化为64n
(2)数学:异或(xor) 不进位加法

基本操作

~取反

运算律

计算完等于自身的

A&A=A
A|A=A
A|0=A
A^0=A

计算完等于0的

A&0=0
A^A=0

取位操作

注意二进制都是从第0位开始!!!
(n>>k)&1 取第k位
x&((1<<k)-1) 取末k位

按位变化

n^(1<<k) 第k位取反
n|(1<<k) 第k位赋1
n&(~(1<<k)) 第k位赋0
x|(1<<k) 右数第k位变1
x|((1<<k)-1) 右数前k-1位都变1

乘除法

a<<1 a*2
a>>1 a/2
1<<a 2^a

交换两整数

swap(int &a,int &b){
	a^=b;
	b^=a;
	a^=b;
}

判断奇偶

提取最低位即可

统计二进制中1的个数

int cnt=0;
while(x){
	x=x&(x-1);//遇到一个1消一次1
	cnt++;
}

【题目整理】

(构造)Trip to the Olympiad

https://codeforces.com/problemset/problem/2057/C

/*
【思路】
最后的和都是二进制的每一位*2
对于二进制每一位:构造0 1 0 或者 1 0 1
【构造题不要想太复杂!】
直接取l到r中最大能被2^k整除的数 然后取这个数-1 然后随便取一个除了这两个数之外的数
【位运算要熟悉!!!】
*/
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
int t;
void solve(){
      ll l,r;
      cin>>l>>r;
      if(l>r) swap(l,r);
      int k=31-__builtin_clz(l^r);//__builtin_clz(l^r)求前导0的个数 k求l到r区间中最高到多少位
      //逆向思维:我不直接求整除 我求整除-1 -> 很方便的能用位运算操作
      int a=l|((1<<k)-1);//从(最高位-1)位每一位都置1(a>=l)
      int b=a+1;//最高位为1 其他都是0:最大能被2^k整除的数
      int c=a==l?r:l;
      cout<<a<<" "<<b<<" "<<c<<endl;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>t;
      while(t--){
            solve();
      }
      return 0;
}

2^20

https://ac.nowcoder.com/acm/contest/66877/D
注意在二进制的范围内讨论2^20的倍数的含义

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int N=100010;
const int mod=1<<20;
int t;
/*
一直*2 乘20次就能为2^20的倍数->答案不会大于20
统计最后几位为0->20减去
->【策略】枚举加的次数,统计乘的次数
*/
void solve(){
      ll n;
      cin>>n;
      n%=mod;
      int res=20;
      //注意需要枚举!!!不能贪心思想只加一次
      for(int i=0;i<=20;i++){
            int val=(n+i)%mod;
            //已经是倍数了就不需要乘了 直接统计答案
            if(!val) res=min(res,i);
            else{
                  //乘法:二进制补0
                  int num=0;
                  while(!(val&1)){
                        val/=2;
                        num++;
                  }
                  res=min(res,20-num+i);
            }
      }
      cout<<res<<endl;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>t;
      while(t--){
            solve();
      }
      return 0;
}

异或与位移【公式简化+逆运算推公式】

https://ac.nowcoder.com/acm/contest/100007/C

公式

image

公式推导

image
->向右/向左移a[i]位->向下取整直接舍去(整数操作)

image
->取模限定范围直接舍去

image
->异或运算的性质

代码

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int n,m,k;
int a[25];
string ss;
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	while(m--){
		cin>>ss;
		//bitset优化 
		bitset<N> s;
		int l=ss.size();
		for(int i=0;i<l;i++){
			s[i]=ss[l-1-i]-'0';
		}
		//求逆运算 
		for(int i=n;i>=1;i--){
			bitset<N> t=s;
			//讨论a[i]正负 
			if(a[i]>0){
				//a[i]为正->相当于左移a[i]位->(逆运算)往右找值 
				for(int j=a[i];j<k;j++){
					//<2的k次方->取不到k 
					//遍历a[i]到k->修正左边的值 
					t[j]=t[j]^t[j-a[i]];
				}
			}
			else{
				//a[i]为负->相当于右移a[i]位->(逆运算)往左找值 
				//遍历k-1(取不到k所以要-1)+a[i](会减)到0->修正右边的值 
				for(int j=k-1+a[i];j>=0;j--){
					t[j]=t[j]^t[j-a[i]];
				}
			}
			//每完成一次更新还原t到s
			s=t; 
		}
		//从高位输出到低位->k-1到0->去掉前缀0
		int flag=0;
		for(int i=k-1;i>=0;i--){
			if(s[i]) flag=1;
			if(flag) cout<<s[i];
		}
		if(!flag) printf("0");
		printf("\n");
	}
	return 0;
}

(构造)小苯的数组构造

https://ac.nowcoder.com/acm/contest/105623/E
注意如果要空出来一位:请用skip轮每一个数字->避免制造0

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
int t;
int n;
ll x,y;
void solve(){
      cin>>n>>x>>y;
      if(n==1){
            if(x==y){
                  cout<<"YES"<<endl;
                  cout<<x<<endl;
            }
            else{
                  cout<<"NO"<<endl;
            }
      }
      else{
            //把每一位都提出来:确定x y
            vector<int> ans(n+1,0);
            int skip=1;//跳掉哪一个数字:用skip累加
            for(int i=0;i<=31;i++){
                  int t1=(x>>i)&1,t2=(y>>i)&1;
                  if(t1==1 && t2==0){//当前这一位有偶数个1
                        if(n%2==0){
                              for(int j=1;j<=n;j++){
                                    ans[j]=ans[j]|(1<<i);
                              }
                        }
                        else{
                              for(int j=1;j<=n;j++){
                                    if(skip==j) continue;
                                    ans[j]=ans[j]|(1<<i);
                              }
                              skip=min(skip+1,n);
                        }
                  }
                  else if(t1==0 && t2==1){
                        cout<<"NO"<<endl;
                        return;
                  }
                  else if(t1==1 && t2==1){//当前这一位有奇数个1
                        if(n%2){
                              for(int j=1;j<=n;j++){
                                    ans[j]=ans[j]|(1<<i);
                              }
                        }
                        else{
                              for(int j=1;j<=n;j++){
                                    if(skip==j) continue;
                                    ans[j]=ans[j]|(1<<i);
                              }
                              skip=min(skip+1,n);
                        }
                  }
            }
            int xx=0,yy=0;
            for(int i=1;i<=n;i++){
                  if(ans[i]<=0){
                        cout<<"NO"<<endl;
                        return;
                  }
                  xx=xx|ans[i];
                  yy=yy^ans[i];
            }
            if(xx!=x || yy!=y){
                  cout<<"NO"<<endl;
                  return;
            }
            cout<<"YES"<<endl;
            for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
            cout<<endl;
      }
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>t;
      while(t--) solve();
      return 0;
}

(贪心)汉堡猪猪分糖果

https://ac.nowcoder.com/acm/contest/112544/F
一般都是从高位往低位贪心
注意100000-1=011111->低位之和高不过高位

题目大意

n个糖分给m个小朋友,求小朋友获得糖的按位与最大值

思路

image

代码

i64 n,m;
void solve(){
    cin>>n>>m;
	i64 ans=0;
	for(i64 i=30;i>=0;i--){
		i64 v=m*(1LL<<i);
		if(n>=v){
			n-=v;
			ans|=(1LL<<i);
		}
		else{
			if(n>=(v-m)){
				i64 k=(n-(v-m)+(1LL<<i)-1LL)/(1LL<<i);
				i64 res=k*(1LL<<i);
				n-=res;
			}
		}
	}
	cout<<ans<<endl;
}
posted @ 2025-01-25 17:51  White_ink  阅读(10)  评论(0)    收藏  举报