首页 > 其他分享 >好用的bitset——暴力的好帮手

好用的bitset——暴力的好帮手

时间:2023-06-02 15:12:46浏览次数:62  
标签:ch int 51 好帮手 bitset 好用 bit1 b1

会持续更新的。

bitset

C++ 的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用 1 bit空间。
bitset的原理大概是将很多数压成一个,从而节省空间和时间, 一般来说bitset会让你的算法复杂度 /32

构造 bitset

一般有下五种:

#include<bits/stdc++.h>
#include<bitset> // 头文件 
using namespace std;
int main(){
	bitset<4> bit1; //无参构造,长度为4,默认每一位为0

	bitset<8> bit2(12); //长度为8,二进制保存,前面用0补充

	string s1 = "1010100";
	bitset<16> bit3(s1); //长度为 16,前面用0补充 

	bitset<10> bit4(string("1010100")); //与上一个相同,长度为 10 

	char s2[] = "101000"; 
	bitset<25> bit5(s2); //长度为 13,前面用0补充,好像要 C++14 以上才能用 
	
	cout << bit1 << endl; //0000
	cout << bit2 << endl; //00001100
	cout << bit3 << endl; //0000000001010100
	cout << bit4 << endl; //0001010100
	cout << bit5 << endl; //0000000000000000000101000 
	return 0;
} 

用字符串构造时,字符串只能包含 '0' 或 '1' ,否则会抛出异常。

构造时,需在<>中表明bitset 的大小(即size)。

在进行有参构造时,若参数的二进制表示比bitset的size小,则在前面用0补充(如上面的栗子);若比bitsize大,参数为整数时取后面部分,参数为字符串时取前面部分:

#include<bits/stdc++.h>
#include<bitset> // 头文件 
using namespace std;
int main(){
	bitset<2> bit1(12); // 12的二进制为1100(长度为4),但bit1的size=2,只取后面部分,即00
	
	string s1 = "100101";
	bitset<4> bit2(s1); // s的size=6,而bitset的size=4,只取前面部分,即1001
    
    char s2[] = "11101";
    bitset<4> bit3(s2); //与bit2同理,只取前面部分,即1110
    
    cout << bit1 << endl;    //00
    cout << bit2 << endl;    //1001
    cout << bit3 << endl;    //1110
	return 0;
} 

可用的操作符

bitset的运算就像一个普通的整数一样,可以进行与(&)、或(|)、异或(^)、左移(<<)、右移(>>)等操作。

#include<bits/stdc++.h>
#include<bitset> // 头文件 
using namespace std;
int main(){
	bitset<4> bit1(string("1001"));
	bitset<4> bit2(string("0011"));
	
	//注意这里赋值操作后值会改变 
	cout << (bit1 ^= bit2) << endl; // 1010 (1001 ^ 0011 bit1对bit2按位异或后赋值给bit1)
	cout << (bit1 &= bit2) << endl; // 0010 (1010 & 0011 按位与后赋值给bit1)
	cout << (bit1 |= bit2) << endl; // 0011 (0010 | 0011 按位或后赋值给bit1)
	//bit1=0011       bit2=0011
	
	cout << (bit1 <<= 2) << endl; //1100 (左移2位,低位补0,有自身赋值)
	cout << (bit1 >>= 1) << endl; //0110 (右移1位,高位补0,有自身赋值)
	//bit1=0110       bit2=0011
	
	cout << (~bit2) << endl; // 1100 (按位取反)
	cout << (bit2 << 1) << endl;  //0110 (左移,不赋值)
	cout << (bit2 >> 1) << endl;  // 0001 (右移,不赋值)
	//bit1=0110       bit2=0011
	
	cout << (bit1 == bit2) << endl; // false (0110==0011为false)
	cout << (bit1 != bit2) << endl; // true  (0110!=0011为true)
	
	cout << (bit1 & bit2) << endl; // 0010 (按位与,不赋值)
	cout << (bit1 | bit2) << endl; // 0111 (按位或,不赋值)
	cout << (bit1 ^ bit2) << endl; // 0101 (按位异或,不赋值)
	return 0;
} 

此外,可以通过 [ ] 访问元素(类似数组),注意最低位下标为0,如下:

#include<bits/stdc++.h>
#include<bitset> // 头文件 
using namespace std;
int main(){
	bitset<4> bit1(string("1011"));
	
	cout << bit1[0] << endl; //1
	cout << bit1[1] << endl; //1
	cout << bit1[2] << endl; //0
	return 0;
} 

当然,通过这种方式对某一位元素赋值也是可以的,例子就不放了。

可用函数

bit1.size() 返回大小(位数)
bit1.count() 返回1的个数
bit1.any() 返回是否有1
bit1.none() 返回是否没有1
bit1.set() 全都变成1
bit1.test(p) 用来检查第 p + 1 位是否为 1
bit1.set(p) 将第p + 1位变成1
bit1.set(p, x) 将第p + 1位变成x
bit1.reset() 全都变成0
bit1.reset(p) 将第p + 1位变成0
bit1.flip() 全都取反
bit1.flip(p) 将第p + 1位取反
bit1.to_ulong() 返回它转换为unsigned long的结果,如果超出范围则报错
bit1.to_ullong() 返回它转换为unsigned long long的结果,如果超出范围则报错
bit1.to_string() 返回它转换为string的结果

例题

P1537 弹珠

这是非常显然的一个布尔背包问题,因为数据规模比较小,可以直接把多重背包拆分成01背包做。

比较朴素的背包转移方程就是:对于第 \(i\) 个物品,枚举所有可能得到的价值 \(j\) 来进行转移:

\[f_{i,j}|=f_{i-1,j-w_i} \]

显然第一维可以滚掉,那么我们就可以使用 bitset 优化dp,假设 f 是一个bitset,它的第x位为1就表示x可以被表示出来:

\[f|=(f<<i) \]

\(f<<i\) 表示取了有这么一些情况,\(f|=\) 就是不取的一些情况,那么 \(f|=(f<<i)\) 就是取与不取两种情况。
同理,只要是bool型背包应该都能用 bitset 优化。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1.2e5;
inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return x * f;
}
int T, sum;
bitset<N> f;
int a[10];
int main(){
	while(++T){
		sum = 0;
		for(int i = 1; i <= 6; ++i) a[i] = read(), sum += i * a[i];
		if(sum == 0) break;
		f.reset();
		printf("Collection #%d:\n", T);
		if(sum & 1){
			printf("Can't be divided.\n\n");
			continue;
		}
		bool flag = 0;
		f.set(0);
		for(int i = 1; i <= 6; ++i){
			for(int j = 1; j <= a[i]; ++j){
				f |= (f << i);
				if(f[sum / 2]) flag = 1;
			}
		}
		if(!flag) printf("Can't be divided.\n\n");
		else printf("Can be divided.\n\n");
	}
	return 0;
} 

P3674 小清新人渣的本愿

考虑用莫队离线操作。
然后用 bitset 维护,每一位x表示 x 这个数字是否存在,记为 b1。
在记录一个 b2 每一位x表示 N-x 这个数字是否存在。
对于 1 操作 ,如果存在\(z-y=x\),可以同时转化成同时存在 \(z\) 和 \(z-x\),那么将 b1 左移 x(表示将每一个数都加上\(x\))与 b1做 & 预算,看是否有 1,有的话就表示存在。
对于 2 操作,b2 中的\(y′\)表示 b1 中的 \(N-y\),要求 \(z + y = x\),那么可以转化成 \(z + N - y'=x\) ,\(z - y' = x - N\) ,就又变成操作 1 的形式了。那么就只要将 b2 右移 \(N-x\)(这里相当于是将 b2 中的每一个数都加了 \(N-x\)) 与 b1 做 & 操作。
对于 3 操作,直接 \(O(\sqrt{n})\) 枚举约数。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5;
inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return x * f;
}
int n, m, len;
bitset<N + 51> b1, b2;
int a[N + 51], ans[N + 51], c[N + 51], pos[N + 51];
struct Query{
	int id, opt, l, r, x;
	bool operator < (const Query &A) const{return pos[l] == pos[A.l] ? (pos[l] & 1 ? r < A.r : r > A.r) : pos[l] < pos[A.l];}
}q[N + 51];
inline void add(int x){
	if(c[x]++ == 0) b1[x] = 1, b2[N - x] = 1;
}
inline void del(int x){
	if(--c[x] == 0) b1[x] = 0, b2[N - x] = 0;
}
int main(){
	n = read(), m = read(); len = sqrt(n);
	for(int i = 1; i <= n; ++i) a[i] = read(), pos[i] = (i - 1) / len + 1;
	for(int i = 1; i <= m; ++i) q[i] = (Query){i, read(), read(), read(), read()};
	sort(q + 1, q + 1 + m);
	for(int i = 1, l = 1, r = 0; i <= m; ++i){
		while(r < q[i].r) add(a[++r]);
		while(l > q[i].l) add(a[--l]);
		while(l < q[i].l) del(a[l++]);
		while(r > q[i].r) del(a[r--]);
		if(q[i].opt == 1) ans[q[i].id] = (b1 & (b1 << q[i].x)).any();
		else if(q[i].opt == 2) ans[q[i].id] = (b1 & (b2 >> (N - q[i].x))).any();
		else{
			for(int j = 1; j * j <= q[i].x; ++j){
				if(q[i].x % j) continue;
				if(b1[j] && b1[q[i].x / j]){
					ans[q[i].id] = 1; break;
				} 
			}
		}
	}
	for(int i = 1; i <= n; ++i) printf("%s\n", ans[i] ? "hana" : "bi");
	return 0;
} 

P5355 [Ynoi2017] 由乃的玉米田

加减乘三个操作上面都已讲。这里主要讲除法。

  • 如果 \(x \ge \sqrt{n}\)​,那么我们可以暴力枚举商,然后判断有没有出现即可。因为这个商 \(\le \sqrt{n}\)​,所以复杂度是正确的。
  • 如果 \(x < \sqrt{n}\),我们可以预处理出 \(x \in [1,\sqrt{n})\) 的答案。对于每个 \(x\) 遍历一遍序列,找出每个 \(1 \le i \le n\) 的离 \(i\) 最近且 \(\le i\) 的 \(res_i​\),满足 \(a_i\)​ 和 $aa_{res_i}​​ 的商为 \(x\),于是每个询问的答案为 \(l\) 是否 \(\le res_{r}\)​。这一部分的时间复杂度为 \(O(n\sqrt{n})\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5;
inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return x * f;
}
int n, m, len, qcnt;
bitset<N + 51> b1, b2;
int a[N + 51], ans[N + 51], c[N + 51], pos[N + 51];
int pre[N + 51], res[N + 51];
vector<int> ql[N], qr[N], qi[N];
struct Query{
	int id, opt, l, r, x;
	bool operator < (const Query &A) const{return pos[l] == pos[A.l] ? (pos[l] & 1 ? r < A.r : r > A.r) : pos[l] < pos[A.l];}
}q[N + 51];
inline void add(int x){
	if(c[x]++ == 0) b1[x] = 1, b2[N - x] = 1;
}
inline void del(int x){
	if(--c[x] == 0) b1[x] = 0, b2[N - x] = 0;
}
inline void solve(){
	for(int x = 1; x <= len; ++x){
		if(qi[x].empty()) continue;
		int l = 0;
		for(int i = 1; i <= n; ++i){
			int y = a[i];
			pre[y] = i;
			if(x * y <= N) l = max(l, pre[x * y]);
			if(y % x == 0) l = max(l, pre[y / x]);
			res[i] = l;
		}
		for(int i = 0; i < qi[x].size(); ++i) ans[qi[x][i]] = (ql[x][i] <= res[qr[x][i]]);
		memset(pre, 0, sizeof(pre)), memset(res, 0, sizeof(res));
	}
}
int main(){
	n = read(), m = read(); len = sqrt(n);
	for(int i = 1; i <= n; ++i) a[i] = read(), pos[i] = (i - 1) / len + 1;
	for(int i = 1; i <= m; ++i){
		int opt = read(), l = read(), r = read(), x = read();
		if(opt == 4 && x <= 300) qi[x].push_back(i), ql[x].push_back(l), qr[x].push_back(r);
		else q[++qcnt] = (Query){i, opt, l, r, x};
	} 
	sort(q + 1, q + 1 + qcnt);
	for(int i = 1, l = 1, r = 0; i <= qcnt; ++i){
		while(r < q[i].r) add(a[++r]);
		while(l > q[i].l) add(a[--l]);
		while(l < q[i].l) del(a[l++]);
		while(r > q[i].r) del(a[r--]);
		if(q[i].opt == 1) ans[q[i].id] = (b1 & (b1 << q[i].x)).any();
		else if(q[i].opt == 2) ans[q[i].id] = (b1 & (b2 >> (N - q[i].x))).any();
		else if(q[i].opt == 3){
			for(int j = 1; j * j <= q[i].x; ++j){
				if(q[i].x % j) continue;
				if(b1[j] && b1[q[i].x / j]){
					ans[q[i].id] = 1; break;
				} 
			}
		}else{
			for(int j = 1; j * q[i].x <= N; ++j)
				if(b1[j] && b1[j * q[i].x]){
					ans[q[i].id] = 1; break;
				} 
		}
	}
	solve();
	for(int i = 1; i <= m; ++i) printf("%s\n", ans[i] ? "yuno" : "yumi");
	return 0;
} 

P6134 [JSOI2015]最小表示

对于每一条边 \((u,v)\),如果从 \(u\) 到 \(v\) 仅存在这条路径,那么这条边一定要保存,否则一定可以删除,因为若存在另一条从 \(u\) 到 \(v\) 的路线,那么一定存在一个不同于 \(u,v\) 的点 \(x\) ,可以使 \(u\) 到 \(x\), \(x\) 到 \(v\),那么显然边 \((u,v)\) 是可以删除的。为什么这样一定正确呢?因为我们保证了原图联通的点在删边操作仍是联通的。

那么接下来就要想办法对每条边判断是否存在点 \(x\) ,我们可以对每个点,处理出这个点可以到的点,以及有哪些点可以到这个点,那么就是可达性统计了。

接下来就是对 \(u\) 和 \(v\),看 \(u\) 可到达的点和可以到 \(v\) 的点是否有相同的就可以了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e4 + 51, M = 2e5 + 51;
inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return x * f;
}
int n, m, dfn, ans; 
int u[M], v[M], in[N], id[N];
vector<int> e1[N], e2[N];
bitset<N> to[N], co[N];
inline void topu(){
	queue<int> q;
	for(int i = 1; i <= n; ++i) if(!in[i]) q.push(i);
	while(!q.empty()){
		int x = q.front(); q.pop();
		id[++dfn] = x;
		for(int y : e1[x])
			if(--in[y] == 0) q.push(y);
	}
} 
int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; ++i){
		u[i] = read(), v[i] = read();
		++in[v[i]];
		e1[u[i]].push_back(v[i]), e2[v[i]].push_back(u[i]);
	}
	topu();
	for(int i = n; i; --i){
		int x = id[i];
		for(int y : e1[x]) to[x][y] = 1, to[x] |= to[y];
	}
	for(int i = 1; i <= n; ++i){
		int x = id[i];
		for(int y : e2[x]) co[x][y] = 1, co[x] |= co[y];
	} 
	for(int i = 1; i <= m; ++i)
		if((to[u[i]] & co[v[i]]) != 0) ans++;
	printf("%d\n", ans);
	return 0;
} 

标签:ch,int,51,好帮手,bitset,好用,bit1,b1
From: https://www.cnblogs.com/jiangchen4122/p/17417675.html

相关文章

  • Java进行内存泄露​ GC 分析都有哪些常用好用的工具
    使用Java语言开发应用程序,虽然JVM帮我们进行了GC收集、清除工作;但是使用不当的话,还是会导致某些对象常驻堆空间无法给垃圾收集器清除,导致内存泄露、内存溢出等情况,今天盘点一下在项目中进行内存泄露分析和GC分析的一些常用、好用的工具。0x01:JDK自带工具在处理内存泄露方面JDK本身......
  • 浏览器不好用?插件来帮忙
    一、目的浏览器本身具备的功能并不完善,不同的用户可以为自己浏览器增加想要功能,使得浏览器更能符合自己的需求,提高浏览器使用的舒适度二、推荐插件AdblockPlusLastPass(密码记录,全平台通用)DarkReader(亮暗主题)…三、安装插件1.Edge浏览器安装插件点击左上角的拼图图标,进入管理拓......
  • 代码重复检查工具——python的使用CPD比较好用,clone digger针对py2
    代码重复检测:cpd--minimum-tokens100--filesg:\source\python\--languagepython>log.txt输出类似:=====================================================================Founda381line(1849tokens)duplicationinthefollowingfiles:Startingatline24of......
  • 好用的制造业erp系统软件有哪些?
    好不好用只有自己知道,别人好用不一定拿来自己就好用,适合自己的才是自己最好用的ERP系统!没有完全相同的企业,每个工厂都有自己独有的特殊性,照抄照搬是上ERP失败的最重要的因素之一,所以选择适合自己的模式产品才是关键! 要想成功的部署ERP最重要的是认知自己,弄清楚自己未来发展......
  • 使用virustotal VT 查询情报——感觉远远没有微步、思科好用,10万条数据查出来5万条都
    1399gitclonehttps://github.com/VirusTotal/c-vtapi.git1400cdc-vtapi/1402sudoapt-getinstallautomakeautoconflibtoollibjansson-devlibcurl4-openssl-dev1407autoreconf-fi1408./configure--enable-examples1409make1410sudomake......
  • sklearn的train_test_split,果然很好用啊!
    sklearn的train_test_split train_test_split函数用于将矩阵随机划分为训练子集和测试子集,并返回划分好的训练集测试集样本和训练集测试集标签。格式:X_train,X_test,y_train,y_test=cross_validation.train_test_split(train_data,train_target,test_size=0.3,random_state=0)......
  • nc这个工具用于伪造c2服务器 做c2初始连接的抓包分析实在是太tm好用了!必要时候配合APA
    DNSSpoofingwithAPATEDNS20thFebruary2015Wannes.ColmanLeaveacommentIfyou quicklywanttofindoutwhatthemalwareinyoursandboxisresolving,youcanuseApateDNS.ThisfreetoolwilllistenforoutgoingDNSrequestsandisabletospoofthe......
  • 【分享】推荐一个非常好用的redis远程连接工具
    推荐一个非常好用的redis远程连接工具蓝奏云地址https://wwsi.lanzoum.com/ig7xZ0xspf0h密码:4vnz二维码:......
  • 【推荐】7款好用开源的自动化安全测试工具
    安全测试是一项工作量十分庞大的工作,需要相关工作人员在短时间内快速完成任务,因此为了提高效率,减少重复性工作带来的麻烦,这时就需要借助安全测试工具来完成。本文为大家汇总了几款好用开源的自动化安全测试工具,快来了解一下吧。1、NmapNmap是一款不错的自动化安全测试......
  • Javase入门|史上最好用的截图工具Snipaste
    在学习Java的时候,有时候老师操作的比较快,通过截图的方式将老师的操作保存下来,以便后期的操作。另外截图之后的图片也可以用于笔记的记录,在笔记当中最好采用图文并茂的方式,这样更加利于知识的回顾。这篇文章详细介绍下snipaste截图工具的下载安装及使用,配合视频效果更佳动力节点老杜......