位运算--二进制

本文介绍了如何利用递归和位运算解决组合枚举问题,包括指数型枚举、组合型枚举和排列型枚举。通过递归实现的深度优先搜索(DFS)策略,结合二进制枚举,展示了如何生成所有可能的选择方案,并给出了相关算法实例。此外,还涉及最短Hamilton路径问题的解决方法,以及在位运算中寻找最大伤害值的策略。这些技巧在解决信息技术领域的组合问题时具有重要价值。
摘要由CSDN通过智能技术生成

递归实现指数型枚举

状压+二进制枚举+dfs

题目描述

从 1∼n这 n (n≤16) 个整数中随机选取任意多个,输出所有可能的选择方案。
输入描述:
一个整数n。

输出描述:

每行一种方案。同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。对于没有选任何数的方案,输出空行。本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
示例1

输入

3

输出

3

2
2 3
1
1 3
1 2
1 2 3

//递归思路
#include<iostream>
using namespace std;
int N;
void dfs(int n,int state)
{
    if(N==n){
        for(int i=0;i<n;i++)
            if(state>>i&1)cout<<i+1<<" ";
        cout<<endl;
        return;
    }
    dfs(n+1,state);//不用n这个数
    dfs(n+1,state|(1<<n));//用n这个数 
}
int main()
{
    cin>>N;
    dfs(0,0);
    return 0;
}
//非递归思路
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;cin>>n;
    for(int state=0;state<1<<n;state++)
    {
        for(int i=0;i<n;i++)
            if(state>>i&1)cout<<i+1<<" ";
        cout<<endl;
    }
    return 0;        
}


递归实现组合型枚举

此题与二进制无关,这题为dfs,与上题相关联

题目描述

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。n>0, 0≤m≤n, n+(n−m)≤25。

输入描述:

两个整数n,m。

输出描述:

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 9 12排在1 3 10 11前面)。

示例1

输入

5 3

输出

1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

#include<iostream>
using namespace std;
int n,m;
int stu[30];
void dfs(int u,int ans)
{
    if(u>n+1)//这时的选择的数字范围已经不在1~n之间了。为了防止程序陷入死循环应当及时结束函数
    return;
    if(ans>=m)//将选择的m个数字输出
    {
        for(int i=1;i<=n;i++)
        if(stu[i]==1)
        cout<<i<<" ";
        cout<<endl;
        return;
    }
    stu[u]=1;//选择数字u
    dfs(u+1,ans+1);//在选择数字u的基础上进行深度优先遍历,这时因为选择了数字u,应当使ans+1.
    //stu[u]=0;//回溯,消除选择数字u带来的影响,便于接下来对不选择数字u进行操作
    stu[u]=2;//不选择数字u
    dfs(u+1,ans);//不在选择数字u的基础上进行深度优先遍历,这时因为没有选择数字u,应当时ans保持不变.
    stu[u]=0;//回溯,消除没有选择数字u带来的影响,便于接下来的操作
}
int main()
{
    ios::sync_with_stdio(false);//关闭输入流同步,提高读写效率
    cin.tie(0);
    cin>>n>>m;
    dfs(1,0);//从数字1开始进行深度优先遍历
    return 0;
} 

递归实现排列型枚举

又是dfs噢噢

题目描述

把 1∼n 这 n(n<10)个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入描述:

一个整数n。

输出描述:

按照从小到大的顺序输出所有方案,每行1个。 首先,同一行相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
示例1

输入

3

输出

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

//第一种,用全排列公式
#include<cstdio> 
#include<algorithm>
using namespace std;
int n,a[9]= {1,2,3,4,5,6,7,8,9};
int main() {
    scanf("%d",&n);
    do {
        for (int i=0; i<n; i++) printf("%d ",a[i]);
        puts("");
    } while(next_permutation(a,a+n));
}
//第二种
#include<bits/stdc++.h>
using namespace std;
int stu[10],a[10];
int n;
void dfs(int u)
{
    if(u==n+1){
        for(int i=1;i<=n;i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    for(int i=1;i<=n;i++)
        if(stu[i]==0){
            stu[i]=1;
            a[u]=i;
            dfs(u+1);
            stu[i]=0;
        }
    
}
int main()
{
    cin>>n;
    dfs(1);
    return 0;
}

//第三种写法
#include<bits/stdc++.h>
using namespace std;
int a[10];
bool vis[11];
int n,tot=0;
void dfs()
{
    if(tot==n){
        for(int i=1;i<=tot;i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0){
            vis[i]=1;
            a[++tot]=i;
            dfs();
            vis[i]=0;
            a[tot--]=0;
        }
    }
}
int main()
{
    cin>>n;
    dfs();
    return 0;
}

最短Hamilton路径

状压dp+二进制

题目描述

给定一张 n(n \leq 20)(n≤20) 个点的带权无向图,点从0 \sim n-10∼n−1标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

输入描述:

第一行一个整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10^710
7
的正整数,记为a[i,j])。
对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且a[x,y]+a[y,z] \geq a[x,z]a[x,y]+a[y,z]≥a[x,z]。

输出描述:

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

示例1
输入

4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0

输出

4

说明

从0到3的Hamilton路径有两条,0-1-2-3和0-2-1-3。前者的长度为2+2+1=5,后者的长度为1+2+1=4

假设:一共有七个点,用0,1,2,3,4,5,6来表示,那么先假设终点就是5,在这里我们再假设还没有走到5这个点,且走到的终点是4,那么有以下六种情况:
first: 0–>1–>2–>3–>4 距离:21
second: 0–>1–>3–>2–>4 距离:23
third: 0–>2–>1–>3–>4 距离:17
fourth: 0–>2–>3–>1–>4 距离:20
fifth: 0–>3–>1–>2–>4 距离:15
sixth: 0–>3–>2–>1–>4 距离:18
如果此时你是一个商人你会走怎样的路径?显而易见,会走第五种情况对吧?因为每段路程的终点都是4,且每种方案的可供选择的点是04,而商人寻求的是走到5这个点的最短距离,而4到5的走法只有一种,所以我们选择第五种方案,可寻找到走到5这个点儿之前,且终点是4的方案的最短距离,此时05的最短距离为(15+4走到5的距离).(假设4–>5=8)
同理:假设还没有走到5这个点儿,且走到的终点是3,那么有一下六种情况:
first: 0–>1–>2–>4–>3 距离:27
second: 0–>1–>4–>2–>3 距离:22
third: 0–>2–>1–>4–>3 距离:19
fourth: 0–>2–>4–>1–>3 距离:24
fifth: 0–>4–>1–>2–>3 距离:26
sixth: 0–>4–>2–>1–>3 距离:17
此时我们可以果断的做出决定:走第六种方案!!!,而此时0~5的最短距离为(17+3走到5的距离)(假设3–>5=5)
在以上两大类情况之后我们可以得出当走到5时:
1.以4为终点的情况的最短距离是:15+8=23;
2.以3为终点的情况的最短距离是:17+5=22;
经过深思熟虑之后,商人决定走以3为终点的最短距离,此时更新最短距离为:22。
当然以此类推还会有以1为终点和以2为终点的情况,此时我们可以进行以上操作不断更新到5这个点的最短距离,最终可以得到走到5这个点儿的最短距离,然后再返回最初的假设,再依次假设1,2,3,4是终点,最后再不断更新,最终可以得出我们想要的答案
2.DP分析:
用二进制来表示要走的所以情况的路径,这里用i来代替
例如走0,1,2,4这三个点,则表示为:10111;
走0,2,3这三个点:1101;
状态表示:f[i][j];
集合:所有从0走到j,走过的所有点的情况是i的所有路径
属性:MIN
状态计算:如1中分析一致,0–>·····–>k–>j中k的所有情况
在这里插入图片描述
状态转移方程:f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j])

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=20,M=1<<N;

int f[M][N],w[N][N];//w表示的是无权图

int main()
{
    int n;
    cin>>n;

    for(int i=0;i<n;i++)
     for(int j=0;j<n;j++)
      cin>>w[i][j];

    memset(f,0x3f,sizeof(f));//因为要求最小值,所以初始化为无穷大
    f[1][0]=0;//因为零是起点,所以f[1][0]=0;

    for(int i=0;i<1<<n;i++)//i表示所有的情况
     for(int j=0;j<n;j++)//j表示走到哪一个点
      if(i>>j&1)
       for(int k=0;k<n;k++)//k表示走到j这个点之前,以k为终点的最短距离
        if(i>>k&1)
         f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);//更新最短距离

    cout<<f[(1<<n)-1][n-1]<<endl;//表示所有点都走过了,且终点是n-1的最短距离
    //位运算的优先级低于'+'-'所以有必要的情况下要打括号
    return 0;
}

作者:灰之魔女
链接:https://www.acwing.com/solution/content/18533/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

起床困难综合症

二进制

题目描述

21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因:在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。正是由于 drd 的活动,起床困难综合症愈演愈烈,以惊人的速度在世界上传播。为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。
历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd 的防御战线由 𝑛 扇防御门组成。每扇防御门包括一个运算 op 和一个参数 𝑡,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为 𝑥 ,则其通过这扇防御门后攻击力将变为 𝑥 op 𝑡 。最终drd 受到的伤害为对方初始攻击力 𝑥 依次经过所有 𝒏 扇防御门后转变得到的攻击力。
由于atm 水平有限,他的初始攻击力只能为 0 到 𝑚 之间的一个整数(即他的初始攻击力只能在 0, 1, … , 𝑚 中任选,但在通过防御门之后的攻击力不受 𝑚 的限制)。为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入描述:

第 1 行包含2 个整数,依次为 𝑛, 𝑚 ,表示drd 有 𝑛 扇防御门,atm 的初始攻击力为 0 到 𝑚 之间的整数。
接下来 𝑛 行,依次表示每一扇防御门。每行包括一个字符串 op 和一个非负整数 𝑡,两者由一个空格隔开,且 op 在前, 𝑡 在后,op 表示该防御门所对应的操作,𝑡 表示对应的参数。

输出描述:

输出一行一个整数,表示atm 的一次攻击最多使 drd 受到多少伤害。

示例1
输入

3 10
AND 5
OR 6
XOR 7

输出

1

说明

atm 可以选择的初始攻击力为 0,1, … ,10。
假设初始攻击力为 4,最终攻击力经过了如下计算
4 AND 5 = 4
4 OR 6 = 6
6 XOR 7 = 1
类似的,我们可以计算出初始攻击力为 1,3,5,7,9 时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此atm 的一次攻击最多使 drd 受到的伤害值为 1。
在这里插入图片描述

考虑位运算的特点:不进位。
于是可以将答案的每一位分开考虑。从高位到低位枚举每一位所选的 情况。若当前位第 位经过一系列运算后结果可以为 ,那就将答案加上 。由于枚举时由高到低,贪心地使高位为 即可(第 位贡献的答案比第 位到第 位贡献的和还要多)。注意累加值不超过 。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
void solved()
{
    bitset<40>one;
    bitset<40>zero;
    one.set();//把二进制位都置成1
    zero.reset();//把二进制位都置成0
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        string s;
        int x;
        cin>>s>>x;
        if(s[0]=='O')one|=x,zero|=x;
        if(s[0]=='X')one^=x,zero^=x;
        if(s[0]=='A')one&=x,zero&=x;
    }
    ll ans=0;
    for(int i=29;i>=0;i--){
        if(zero[i]==1)ans+=(1<<i);
        else if(one[i]==1&&(1<<i)<=m)ans+=(1<<i);
    }
    cout<<ans<<endl;
}
int main()
{
   solved();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值