C++ 中的 bitset

C++ 中的 \(\textsf{bitset}\) 是能够存储 \(01\) 的容器,这一点看似与布尔(bool)数组很像。而一个布尔类型将会占用 \(1\) 字节的空间,相对于 \(\textsf{bitset}\) 来讲 \(1\) 字节的空间将可以存储 \(8\) 位的 \(01\)

另外,\(\text{bitset}\) 的原理,是通过几个 unsigned long 做到压位的。

使用 \(\text{bitset}\)

和一般的 STL 一样,需要一个特定的头文件进行引用,但是它并不属于 STL 的哪一类。

#include <bitset>

\(\text{bitset}\) 的相关使用

1、定义

bitset <10010> BS; //10010 作为bitset的大小,下标从0开始算起,BS中都为0
bitset <10010> BS(val); //同样是 10010 位的bitset,而bitset中以及有了 val 的二进制数
                        // 例如,bitset <4> BS(3) 中BS的值为 0011

const string str = "101"; // str 作为一个【字符串常量】是一个01串!
bitset <10010> BS(str); // 例如,str = "101", bitset <4> BS(str) 中BS的值为 0101

bitset <10010> BS(string("101"));// 或者也可以这样写 

对于整数来说,当 \(\textsf{bitset}\) 的大小小于 \(\textsf{val}\) 的值得时候,会只存入 \(\textsf{val}\) 的后面的二进制数,就像是 \(9(2) = 1001\), 而 bitset <3> BS 中的元素仅会是 \(001\)

对与字符串来说,恰恰相反:const string str = "110101";

bitset <6> BS; // BS的值为 110101
bitset <5> BS; // BS的值为 11010
bitset <4> BS; // BS的值为 1101  ...

它是从前往后存的。

还有,bitset <10010> BS(0)bitset <10010> BS,均可使初始化为 \(0\)


2、运算

  • \(\textsf{bitset}\) 能与数组一样将每个元素进行调用,例如:当BS的值为 \(010110\) 时,BS[0] = 0, BS[1] = 1, BS[2] = 1, BS[3] = 0, BS[4] = 1, BS[5] = 0}

  • \(\textsf{bitset}\) 可以用于左移右移、与、或、异或、非等( &、|、^、~、&=、|=、^=、>>、<<、>>=、<<= )的各种位运算操作,但是两个对象仅能为 \(\textsf{bitset}\)
    若两个 \(\textsf{bitset}\) 的大小不一样,那么这两个 \(\textsf{bitset}\) 是无法做到位运算的。
    对于左移右移超过了其最大位,也只会保留最后的没有超过的位数。

  • \(\textsf{bitset}\) 可以用 ==、!= 与其他变量进行比较,返回 false/true。(与实数比较时,会默认将实数当做整数比较)

  • \(\textsf{bitset}\) 流运算符,这意味着你可以通过 cin/cout 进行输入输出。


3、函数

使用 作用
BS.count() 返回 true 的数量
BS.any() 若存在某一位是 true 则返回 true,否则返回 false
BS.none() 若所有位都是 false 则返回 true,否则返回 false
BS.all() 若所有位都是 true 则返回 true,否则返回 false
BS.size() 返回 bitset 的大小
BS._Find_first() 返回 bitset 第一个 true 的下标,
若没有 true 则返回 bitset 的大小
BS._Find_next(pos) 返回 pos 后面(严格)第一个 true 的下标,
pos 后面没有 true 则返回 bitset 的大小
使用 作用
BS.set() 将整个 bitset 设置成 true
BS.set(pos,true/false) 将某一位设置成 true/false
BS.reset() 将整个 bitset 设置成 false
BS.reset(pos) 将某一位设置成 false。相当于 set(pos, false)
BS.flip() 翻转每一位,相当于异或一个全是 \(1\)bitset
BS.flip(pos) 翻转某一位
使用 作用
BS.to_string() 返回转换成的字符串表达
BS.to_ulong() 返回转换成的 unsigned long 表达
BS.to_ullong() 返回转换成的 unsigned long long 表达

例题

题目:[Ynoi2017] 由乃的玉米田P3674 小清新人渣的本愿

这个题的做法是这样的:

使用莫队来解决本题。

首先,暴力维护 \(x\) 是肯定行不通的。那么,来考虑每个操作:

  • 对于减法操作,\(a-b=x\),即 \(a=b+x\)。其实可以转化为判断存在的问题,即枚举 \(b\),再判断 \(b+x\) 是否存在即可。
    这个过程相当于将 check[] 数组集体向右平移 \(x\),可以考虑维护一个值域的 bitset,做 s & (s << x) 的操作,判断是否有 \(b\)\(b+x\) 同时存在。

  • 对于加法操作,\(a+b=x\),考虑转化为减法的操作。
    令两次同时减去常数 \(V\)(这里 \(V\) 设为值域),得到 \(a-(V-b)=x-V\),即 \(a=x-V+(V-b)\),这里再维护一个 bitest 的第 \(i\) 位维护 \(V-i\) 是否出现过。由于这里 \(V\geq x\), 所以做 s1 & (s2 >> (V - x)) 操作即可。

加减法的操作均用到了 bitset 的操作所以这部分的复杂度为 \(O(\frac{V}{w})\) 的。

  • 对于乘法操作,无法用 bitset 类似与加减法的维护。但可以通过 \(O(\sqrt[2]x)\) 的复杂度枚举 \(x\) 的因子,判断。
  • 对于除法操作,
    • 如果用类似与乘法操作的思路枚举 \(x\) 的倍数 \(ax\),然后再判断 \(a\) 是否出现过,但是这样的复杂度为 \(O(\frac{V}{x})\),在 \(x\leq \sqrt[2]V\) 的时候,复杂度就炸掉了。
    • 那么对于 \(x\leq \sqrt[2]V\) 的部分,做法是将这一部分的 \(x\) 全部预处理。即,对于每个 \(x\) 以及每个 \(i\),维护 \(p_x(i)\) 表示为 \(a_i\)\(a_{p_x(i)}\) 的商为 \(x\) 的距离 \(i\) 最近的下标 \(j\),且 \(j\leq i\)
      答案即是在 \([l,r]\) 中是否存在一个 \(i\) 使得 \(p_x(i)\geq l\),又因为当 \(i<l\) 是的 \(p_x(i)\) 必然小于 \(l\),对 \(p_x(i)\) 做一个前缀最大值即可,复杂度为 \(O(n\sqrt[2]V)\)
参考代码
#include <bits/stdc++.h>

const int N = 1e5;
const int V = pow(1e5, 0.5);

struct qry {
	int l, r, x, id, opt;
	qry() {}
	qry(int il, int ir, int iid, int ix = 0, int ip = 0) :
	l(il), r(ir), x(ix), id(iid), opt(ip) {} 
} q[N + 10];
std::vector<qry> g[V + 10];

std::bitset<N + 10> s1, s2;
int cnt[N + 10], a[N + 10], p[N + 10], lst[N + 1], n, m, m1;
bool ans[N + 10];

void InitQuery()
{
	for (int i = 1, opt, l, r, x; i <= m; i ++ ) {
		std::cin >> opt >> l >> r >> x;
		if (opt == 4 && x <= V) g[x].push_back(qry(l, r, i));
		else q[++ m1] = qry(l, r, i, x, opt);
	}
	std::sort(q + 1, q + m1 + 1, [k = n / sqrt(m1)](const qry&x, const qry&y) -> bool {
		int lx = x.l / k, ly = y.l / k;
		return lx == ly ? lx & 1 ? x.r > y.r : x.r < y.r : lx < ly;
	});
	return ;
}

void Add(int x) {if (! (cnt[x] ++ )) s1[x] = s2[N - x] =  true;}
void Del(int x) {if (! ( -- cnt[x])) s1[x] = s2[N - x] = false;}

int main()
{
	std::cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		std::cin >> a[i]; 
	}
	
	InitQuery();
	
	for (int i = 1, l = 1, r = 0; i <= m1; i ++ ) {
		while (l > q[i].l) Add(a[ -- l]);
		while (r < q[i].r) Add(a[ ++ r]);
		while (l < q[i].l) Del(a[l ++ ]);
		while (r > q[i].r) Del(a[r -- ]);
		int x = q[i].x;
		switch (q[i].opt) {
			case 1 : ans[q[i].id] = (s1 & (s1 << x)).any();       break;
			case 2 : ans[q[i].id] = (s1 & (s2 >> (N - x))).any(); break;
			case 3 :
				for (int j = 1; j * j <= x; j ++ ) {
					if (x % j) continue;
					if (j == (x / j) && s1[j] && cnt[j] > 1) {ans[q[i].id] = true; break;}
					else if (s1[j] && s1[x / j]) {ans[q[i].id] = true; break;}
				}
				break;
			case 4 :
				for (int j = 1; j * x <= N; j ++ ) {
					if (s1[j * x] && s1[j]) {ans[q[i].id] = true; break;}
				}
				break;
		}
	}
	
	for (int x = 1; x <= V; x ++ ) {
		if (g[x].empty()) continue;
		memset(p, 0, sizeof p);
		memset(lst, 0, sizeof lst);
		for (int i = 1; i <= n; i ++ ) {
			p[i] = p[i - 1];
			lst[a[i]] = i;
			if (a[i] % x == 0) p[i] = std::max(p[i], lst[a[i] / x]);
			if (a[i] * x <= N) p[i] = std::max(p[i], lst[a[i] * x]);
		}
		for (qry tmp : g[x]) ans[tmp.id] = tmp.l <= p[tmp.r];
	}
	
	for (int i = 1; i <= m; i ++ ) puts(ans[i] ? "yuno" : "yumi");
	return 0;
}
posted @ 2023-03-04 22:56  Ciaxin  阅读(81)  评论(0编辑  收藏  举报