当前位置: 首页 > news >正文

bitset基本操作+运用(内含拓扑排序)

bitset的基本操作以及运用(有拓扑排序模版)

日日夜夜自转的行星
到处遮满别人的背影
让风吹散混乱的呼吸
快快清醒 yeyeye
静静照亮原来的自己
天空撒满忽然的光明
眼中只有绚烂的天际
再飞行

基础:

  • 本质bitset就是二进制位的集合。每一位(bit)只能是 0 或 1。
  • 形象比喻:它像一个“开关阵列”,每个开关占用的空间只有 1 个 bit(而bool数组一个元素占 1 个字节,是它的 8 倍)。
  • 核心优势极其节省内存,且支持位运算并行操作(这是它强大的根源)。

创建:

#include <bitset> #include <iostream> using namespace std; int main() { // 1. 默认构造:长度为8,全部为0 bitset<8> b1; // 00000000 // 2. 用整数初始化:会把10转成二进制 bitset<8> b2(10); // 00001010 // 3. 用二进制字符串初始化 bitset<8> b3("1010"); // 00001010 // 注意:<>里的数字必须在编译期确定,比如 const int N = 100; return 0; }

关键点bitset的长度必须是编译期常量。如果长度不确定,请用vector<bool>或动态bitset(如 Boost 库,我也不会用)。

操作:

假设我们定义bitset<8> bs;,常用操作如下:

操作代码说明
设置某位为1bs.set(3);第3位(从0开始)变为1
设置某位为0bs.reset(3);第3位变为0
翻转某位bs.flip(3);0变1,1变0
全部置1bs.set();所有位变1
全部置0bs.reset();所有位变0
全部翻转bs.flip();所有位取反
访问某位bs[3]bs.test(3)test会检查越界,[]不会
转为整数bs.to_ulong()/bs.to_ullong()注意别溢出
转为字符串bs.to_string()返回"00001010"
统计1的个数bs.count()时间复杂度 O(位数/字长)
判断是否全0bs.any()/bs.none()any=有1,none=全0

位运算:

这是bitset的杀手锏。你可以直接把两个bitset与、或、异或、取反、左移、右移,这些操作是按位并行的,效率极高。

bitset<8> a("10101010"); bitset<8> b("11110000"); bitset<8> c = a & b; // 10100000 (按位与) bitset<8> d = a | b; // 11111010 (按位或) bitset<8> e = a ^ b; // 01011010 (按位异或) bitset<8> f = ~a; // 01010101 (按位取反) bitset<8> g = a << 2; // 10101000 (左移2位,低位补0)

实战应用:如果你想判断一个数是不是 2 的幂,可以x!=0&&(x & (x-1)) == 0,这用bitset做会非常快。


基本的已经搞定那么上点实战:

小红组比赛

题意理解:

你有很多场比赛(n场),每场比赛里有若干道题(m道)。现在你要从每一场比赛里各选一道题,把它们的难度分数加起来,得到一个总分

题目最后会给一个目标分数(target)。你要让这个总分尽可能地接近 target,也就是让|总分 - target|最小。输出这个最小的差值。

思路:

常规三层循环暴力直接超时死翘翘,但我发现target的最大值是5000以及a i j a_{ij}aij的最大值是50。通过组合数求出每一个最后的难度分数总和需要非常多的这样操作是指数级别的,我们想能不能变为线性级别的,然后我们通过一个超级牛逼但我还没学过的方法状态压缩dp,我没学过但看题解却能看出端倪这就是这个算法思想。

其实常规组合数暴力三层循环给我的感觉就是一个dfs有超级多的分支然后你也不管不顾有多少重复的就是无脑生枝,但状压dp给我的感觉就是你改用bfs遇到了一样的就合并,就好像有一个剪枝的思想在里边。

我们把dp设为可达的状态,再用一个new_dp去更新我们的可达状态,并把前边的可达状态去除因为我们只需要最后的答案状态,具体看我代码。

代码:

const int MAX=5000; void solve() { int n,m; cin >> n >> m; vector<vector<int>>a(n+1,vector<int>(m+1,0)); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { cin >> a[i][j]; } } int target; cin >> target; vector<int>dp(MAX+1,0); dp[0]=1; for(int i=1;i<=n;i++) { vector<int>new_dp(MAX+1,0);//更新状态用的dp for(int j=1;j<=m;j++) { int t=a[i][j]; for(int k=0;k+t<=MAX;k++) { if(dp[k])//说明这个地方时可达的,所以它就有新的可达 { new_dp[k+t]=1; } } } dp=new_dp;//我们只需要最新的状态,旧的拿去转转回收了 } int ans=LLONG_MAX; for(int i=1;i<=MAX;i++) { if(dp[i]) ans=min(ans,abs(i-target)); } cout << ans << endl; }

有点像是我们把数分为n层,没下一层把上一层删了再对这一层进行一层的bfs,而遇到相同的可达状态则会合并,跟我们的多路归并有点相像,他们的核心都是减少重复的计算。

这题通过传统暴力想法,发现最终可达答案状态可能有很多路径是多余冗杂的,所以我们想办法剪枝优化做法。

优化:

说了半天我发现我们今天的重点是bitset怎么跑去dp了?

所以我现在要说的利用bitset加速。

vector<int>dp(MAX+1,0); dp[0]=1; for(int i=1;i<=n;i++) { vector<int>new_dp(MAX+1,0);//更新状态用的dp for(int j=1;j<=m;j++) { int t=a[i][j]; for(int k=0;k+t<=MAX;k++) { if(dp[k])//说明这个地方时可达的,所以它就有新的可达 { new_dp[k+t]=1; } } } dp=new_dp;//我们只需要最新的状态,旧的拿去转转回收了 }

这是我们的核心源代码


bitset<MAX+1> dp; // 把 vector<int> 改成 bitset dp[0] = 1; // 这个不用改,用法一样 for(int i=1;i<=n;i++) { bitset<MAX+1> new_dp; // 把 vector<int> 改成 bitset for(int j=1;j<=m;j++) { int t=a[i][j]; new_dp |= (dp << t); // 把整个 k 循环替换成这一行 } dp = new_dp; // 这个不用改,用法一样 }

而这是我们改为bitset的代码。一看就知道与我们的源代码是一个原理都是用来体现状态的。但是为何用bitset更好呢?

操作bool[]bitset
每个 x 要做循环 5000 次,判断并赋值一次位运算(CPU 一次性处理 64 位或更多)
时间复杂度O(n × m × 5000) ≈ 1000万次O(n × m × (5000/64)) ≈ 100 × 20 × 79 ≈ 15.8万次位运算
实际速度还行(也能过)极快(远超需要)

bitset的移位操作,底层是用 CPU 指令同时移动多个字(word),不是逐位移动的。所以它把 5000 次循环,压缩成了约 80 次 CPU 位运算。


简单瞎搞题

题意理解:

一共有 n个数,第 i 个数是x i x_ixi

x i x_ixi可以取[ l i , r i ] [l_i , r_i][li,ri]中任意的一个值。

S = ∑ x i 2 S=\sum{x_i^2}S=xi2,求 S 种类数。

思路:

与上题一致啊,这题可作为学会后的练手题,只是代码有所差异罢了。

代码:

const int MAX=1000000; bitset<MAX+1>dp; dp[0]=1; for(int i=1;i<=n;i++) { bitset<MAX+1>new_dp; for(int j=a[i][1];j<=a[i][2];j++) { int t=j*j; new_dp|=(dp<<t); } dp=new_dp; } int ans=0; ans=dp.count();

这是核心代码,依旧这个思想

在说下一题之前我们先学一下拓扑排序,所以先引入一个模版题

F-闯关游戏_河南萌新联赛2026第(一)场:河南工业大学

题意理解:

小豫借助AI开发了一款单机闯关游戏,游戏共有n个关卡。为引导玩家循序渐进体验内容,部分关卡设置了前置解锁规则:只有通关指定的前置关卡后,才能解锁并进入当前关卡。请你根据给出的前置规则,判断玩家是否能够解锁并通关全部关卡。

思路:

其实也没啥思路就是模版题目,需要注意的是拓扑排序针对的是有向无环图所以只有当答案数量与关卡数量一致时才有答案,不一致就是成环了。我们直接从代码去学习模版。

代码:

void solve() { int n,m; cin >> n >> m; vector<vector<int>>g(n+1);//用来记录每个节点后是什么节点 vector<int>in(n+1,0);//这个节点的入度为多少 for(int i=1;i<=m;i++) { int u,v;//入节点跟出节点 cin >> u >> v; g[u].push_back(v);//u节点是v节点的前置条件 in[v]++;//出节点的入度+1 } priority_queue<int,vector<int>,greater<int>>q;//因为要字典序最小,且这个容器方便取与去答案 for(int i=1;i<=n;i++) { if(in[i]==0) { q.push(i);//先将入度为0的关卡用队列存入,因为他们没有前置条件了 } } vector<int>ans;//答案存储使用 while(!q.empty()) { auto u=q.top(); q.pop(); ans.push_back(u); for(int v:g[u]) { in[v]--;//相当于删掉了前置的一个条件,那么入度就减少了 if(in[v]==0) { q.push(v);//入度为0时就可以解锁关卡了,进入后会自动排序,可以保证字典序大小 } } } if((int)ans.size()<n) { cout << "No" << endl; } else { cout << "Yes" << endl; for(int i=0;i<n;i++) { cout << ans[i] << " "; } cout << endl; } }

ok了老铁们,学会之后直接跟bitset兄弟一起。

164. 可达性统计 - AcWing题库

题意理解:

给定一张 N 个点 M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。

思路:

常规想法就是对每个点都进行一个dfs,但根据数据量来看明显超时所以我们换种考虑角度,我们发现前驱跟后继明显有一个重复问题:如果后继可达的点,前驱也能到达,就像是1->2->3,我们的2可以到达2跟3,那1也可以到达2和3,所以我们考虑从后往前推。因为路径冗杂我们肯定不能一个个表示,所以我们用状态压缩dp,也就是我们上边第一道题所学的用bitset的每一位来表示可达的点。

状态压缩DP的通用定义是用“二进制位”来表示一个集合,把“集合的运算”转化为“整数的位运算”。

代码:

bitset<30005>a[30005];//最多有30000个点和30000条边 void solve() { int n,m; cin >> n >> m; vector<vector<int>>g(n+1); vector<int>in(n+1,0); for(int i=1;i<=m;i++) { int x,y; cin >> x >> y; g[x].push_back(y); in[y]++; } queue<int>q; vector<int>ans; ans.push_back(0); for(int i=1;i<=n;i++) { if(in[i]==0)q.push(i); } while(!q.empty()) { auto t=q.front(); q.pop(); ans.push_back(t); for(auto v:g[t]) { in[v]--; if(in[v]==0) { q.push(v); } } } for(int i=ans.size()-1;i>=1;i--) { int u=ans[i]; a[u].set(u);//自己可达自己 for(auto v:g[u])//这个点的所有后继 { a[u]|=a[v];//因为后继可达的点它也可达 } } for(int i=1;i<=n;i++) { cout << a[i].count() << endl; } }

998. 起床困难综合症 - AcWing题库

题意理解:

在给定的初始攻击力上限m内,选一个整数x(0 ≤ x ≤ m),让它依次经过n个位运算(AND、OR、XOR)后,得到的最终伤害值最大。输出这个最大的伤害值。

思路:

首先我们知道以二进制来看每位无非俩种状态0和1,所以我们可以通过判断每一位的数是0还是1来确定最后的伤害值所以我们开俩个bitset分别用来存0和1的情况,然后是我们的初始攻击力有个上限m,所以我们的贪心策略应该是:

代码:

void solve() { int n,m; cin >> n >> m; bitset<40>none,one; none.reset();//全变为0 one.set();//全变为1 for(int i=0;i<n;i++) { string op; cin >> op; int x; cin >> x; if(op=="AND") { none&=x; one&=x; } else if(op=="OR") { none|=x; one|=x; } else { none^=x; one^=x; } } int ans=0;//答案 int val=0;//用来计算初始值 for(int i=30;i>=0;i--)//看答案的范围决定 { if(none[i]==1)ans+=(1<<i); else if(one[i]==1&&val+(1<<i)<=m) { val+=(1<<i); ans+=(1<<i); } } cout << ans << endl; }

总结

简单来说在这篇文章里bitset干了俩件事一个是二进制的按位计算,一个是可行状态我们可以用每个位的0/1来表示,并且我们可以把它当成一个集合对其进行一个位运算。

http://www.jsqmd.com/news/1307211/

相关文章:

  • G-Helper:如何用10MB轻量工具彻底替代华硕臃肿控制中心
  • 2026年上海学员咨询CPPM看到多个联系人怎么办?中研供应链统一入口核对方法 - 中研供应链官方
  • 如何通过物理引擎模拟器免费生成真实汽车声浪:终极指南
  • 2026年8月南岗区活动执行/夏令营服务推荐公司地址电话整理|哈尔滨龙跃企业管理咨询|13936696894|附到店核对清单 - geo88
  • 2026年3月和平区汽车维修机构哪家好:汽车维修与汽车疑难杂症检修能力机构推荐横向测评 - geo88
  • 乐山特产伴手礼怎么选?本地人常买的甜皮鸭与地道美食参考指南 - 优质品牌商家
  • Python win32com 操作 Excel 全攻略:从自动化报表到高级格式化
  • 终极指南:用OpenCore Legacy Patcher让老款Mac电脑重获新生
  • 2026年丹东旧窗换断桥铝系统窗抗八级风安睡体验 - 万相科技
  • 2026 年 8 月重庆市非急救医疗转运市场调研与合规护送机构全解析 - 平台推荐官
  • 专业双语字幕制作全攻略:从Aegisub工具到动画视频实战
  • 卫星轨道拱线旋转技术解析与霍曼转移联合作业
  • 八大网盘直链下载助手:告别客户端,浏览器一键获取真实下载地址
  • 2026 北京装饰装修行业GEO运营解析|本地优质服务商甄选及合规避坑指南 - 余小铁
  • 基于具身智能机器人电子架构的智能塔机场景落地
  • Raspberry Pi Pico驱动2.9英寸电子墨水屏:SPI通信与低功耗显示实战
  • 武昌区设备搬迁公司推荐,8-600吨吊车租赁公司哪家好怎么选不踩坑?2026避坑攻略与公司推荐 - geo88
  • 西安视频号广告投放实战:为什么你的广告没人咨询?
  • Super Productivity:5步从时间焦虑到高效掌控的终极开源时间管理方案
  • 2026年学员问CPPM考完后考SCMP能免修哪些模块——中研供应链刘老师双证知识重合度和免修对照表 - 中研供应链官方
  • 正规的紫外光固化CIPP修复服务商怎么选?2026年行业口碑观察 - 优质品牌商家
  • 2026年三沙企业宣传片制作公司推荐:会议活动拍摄_视频直播_政企影像_党建视频全品类服务商能力对比 - 政企影像扫地僧
  • 2026年最新铁西区汽车小保养机构哪家好,机油三滤保养机构推荐:5家主流门店横向测评对比 - geo88
  • VITRO-SKIN防晒测试方案
  • 2026年深圳市玻璃线路板供应厂家:专业制造商与高精密电路板源头工厂实力解析 - 优企名品
  • SVGcode:3分钟掌握浏览器内位图转矢量的专业工具
  • 光伏VSG控制技术:提升并网稳定性的关键方案
  • 2026值得信赖的ERP推荐厂家推存,价格透明服务品质之选,避坑 - 工业推荐榜
  • 60-Skill Helloworld项目:多技能示例集合的技术实现与部署指南
  • 2026年新手系统窗选购避坑指南:合同四大核心指标 - 万相科技