bitset
bitset
序
从暑假开始就一直听到 优化, 而且好像还挺厉害, 虽然只是常数优化, 但是却非常的好用.
bitset 是啥
其实就是一个二进制数, 包含在 库里(万能头也有), 声明如下:
bitset <N> B;
表示声明了一个位长为 的 , 所占用的内存也仅仅是 位, 也就是说, 的每一位占用仅仅是 位, 与同样是表示 的 相比, 小了 倍 ( 最小时是 , 一位只要 ).
所以当我们只需要 时, 是不二之选, 空间复杂度大大减小.
不止能优化空间, 还能优化时间, 我们 个 做位运算, 时间复杂度是 , 而一个 位的 做位运算, 时间复杂度仅为 . 这个 是一个常数, 取决于你的计算机位数, 如果你是 位系统, 那就是 , 位系统就是 .
尽管只是常数优化, 但是这个优化已经非常大了, 可以让我们原本复杂度 左右的代码卡到 左右.
bitset 咋用
支持的操作:
B = 10; //赋值
B[0]; // 访问某一位
/* 相同大小的 bitset 可以进行运算符操作 */
/* == != & &= | |= ^ ^= ~ << <<= >> >>= */
B.count(); // 返回 1 的数量
B.size(); // 返回 bitset 的大小
B.any(); // 若有 1, 返回 1
B.none(); // 若无 1, 返回 1
B.all(); // 若全是 1, 返回 1
B.set(); // 全部设为 1
B.set(pos, k); // 将第 pos 位设为 k
B.reset(); // 全部设为 0
B.flip(); // 翻转每一位
B.flip(0); // 翻转某一位
主要是应用于我们的状态仅有 两种时, 就我见过的题来看, 通常是 .
就以今天模拟赛的一道区间 为例, 详细的讲解一下 到底咋用.
题目给出 个人按照 的顺序站成一排, , 给出 个人之间 的胜负关系, 求哪些人最终可以赢.
.
.
.
.
.
.
时间限制:
空间限制:
我一上来想成了拓扑排序 + 缩点, 后来发现不行, 然后觉得是个 , 于是开始想 .
想了好久, 感觉是个区间 , 但是暂时没有头绪, 所以我就干脆点, 先把状态设出来, 于是状态就出来了: 表示 在 中能不能赢.
先把状态设出来, 管他会不会转移.
然而状态出来之后, 转移也就自然而然的出来了.
设 表示 与 的结果.
我们考虑在区间 中, 什么情况下 才能赢, 于是我们找到了 赢的充要条件: 当且仅当在 中存在能够被 打败的人, 并且这个人能够在 中赢, 也就是 , 右边也是同理, .
因为需要枚举 , 所以复杂度 . 期望得分 .
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * f;
}
const int N = 705;
int n, a[N][N], f[N][N][N];
char s[N];
int main() {
n = read();
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= n; j++) {
a[i][j] = s[j] ^ 48;
}
}
for (int i = 1; i <= n; i++) f[i][i][i] = 1;
for (int i = 1; i < n; i++) {
f[i][i + 1][i] = a[i][i + 1];
f[i][i + 1][i + 1] = a[i + 1][i];
}
for (int l = 3; l <= n; l++) {
for (int i = 1, j = l; j <= n; i++, j++) {
for (int k = i; k <= j; k++) {
if (k == i) {
for (int o = i + 1; o <= j; o++) {
if (a[k][o] && f[i + 1][j][o]) {
f[i][j][k] = 1;
break;
}
}
} else if (k == j) {
for (int o = i; o < j; o++) {
if (a[k][o] && f[i][j - 1][o]) {
f[i][j][k] = 1;
break;
}
}
} else {
if (f[i][k][k] && f[k][j][k]) f[i][j][k] = 1;
}
}
}
}
for (int i = 1; i <= n; i++) {
if (f[1][n][i]) printf("%d ", i);
}
return 0;
}
在还有十几分钟的时候, 本来我都放弃优化了的, 已经关掉我的 去 了, 然后突然, 我在给别人讲述我的思路的时候, 他重复了一下我的思路, "也就是说, 只要 能在 中赢, 并且能在 中赢就行是吧." , "woc! 你是天才!" 然后我突然就悟了!!!我说我会 的了, 然后想了一会, 好像不是 , 仔细思索片刻, 确实是 , 但是由于我太菜了, 有人拿我当蛐蛐, 押宝, 赌我能不能写出来. 由于非常好写, 我直接打开 , 不到 就写完了, 然后直接提交, 激动片刻, 没过大样例, 但是时间快了很多, 我还在想是不是只是常数优化? 然后打开一看, , 我直接打开 , 把 的状态改成了 , 然后提交, 漂亮! 过了. 那个人已经走了, 那个瞧我不起的人. 我的心脏怦怦直跳...一路从五楼冲到一楼, 吃饭去了.
其实很简单, 状态不变, 我们发现我们其实没必要枚举 , 因为我们已经知道了 在 中以及 中能不能赢了, 所以我们就可以 转移了, 只需要特判一下 以及 的情况就行了.
复杂度 , 期望得分 .
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) +(ch ^ 48);
return x * f;
}
const int N = 705;
int n, a[N][N];
bool f[N][N][N];
char s[N];
int main() {
n = read();
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= n; j++) {
a[i][j] = s[j] ^ 48;
}
}
for (int i = 1; i <= n; i++) f[i][i][i] = 1;
for (int i = 1; i < n; i++) {
f[i][i + 1][i] = a[i][i + 1];
f[i][i + 1][i + 1] = a[i + 1][i];
}
for (int l = 3; l <= n; l++) {
for (int i = 1, j = l; j <= n; i++, j++) {
for (int k = i + 1; k < j; k++) f[i][j][k] = f[i][k][k] & f[k][j][k];
for (int k = i + 1; k <= j; k++) {
f[i][j][i] = a[i][k] & f[i + 1][j][k];
if (f[i][j][i]) break;
}
for (int k = i; k < j; k++) {
f[i][j][j] = a[j][k] & f[i][j - 1][k];
if (f[i][j][j]) break;
}
}
}
for (int i = 1; i <= n; i++) {
if (f[1][n][i]) printf("%d ", i);
}
return 0;
}
接下来考虑正解, 其实正解已经不需要脑子了, 只可惜我当时不会 , 加之过于激动, 就没想.
我们想一下, 我们的状态存的都只是 , 所以我们完全可以开一个 , 这样首先就解决了我们内存的问题, 因为 是开不下 的.
然后我们看一下我们的转移: .
我们发现 是这三个变量的共同值, 我们就可以开两个 , 表示 在 里能不能赢, 表示 在 里能不能赢, 这样我们就可以用 和 来分别表示 和 了, 由于第三维相同, 所以转移可以写成: . 然后特判一下 和 的情况就行了, 顺便处理 和 .
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * f;
}
const int N = 2005;
int n;
bitset <N> f[N][N], f1[N], f2[N], a[N];
char s[N];
int main() {
n = read();
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= n; j++) {
a[i][j] = s[j] ^ 48;
}
}
for (int i = 1; i <= n; i++) f[i][i][i] = 1;
for (int l = 2; l <= n; l++) {
for (int i = 1, j = l; j <= n; i++, j++) {
f[i][j] = f1[i] & f2[j];
if ((f[i + 1][j] & a[i]).any()) f[i][j][i] = f2[j][i] = 1;
if ((f[i][j - 1] & a[j]).any()) f[i][j][j] = f1[i][j] = 1;
}
}
for (int i = 1; i <= n; i++) {
if (f[1][n][i]) printf("%d ", i);
}
return 0;
}
转移 , 复杂度 , 期望得分 .
总结
其实 真的是个好东西, 当我们的操作只需要两个数组进行位运算, 并且状态只有 时, 复杂度可以在常数上有非常大的优化.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】Flutter适配HarmonyOS 5知识地图,实战解析+高频避坑指南
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合终身会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次SSD性能瓶颈排查之路——寿命与性能之间的取舍
· 理解 .NET 结构体字段的内存布局
· .NET 9中的异常处理性能提升分析:为什么过去慢,未来快
· 字符集、编码的前世今生
· Web性能优化:从 2 秒到200毫秒
· 【故障公告】博客主站遭遇很奇怪的疯狂攻击
· 我与博客园的20年
· 刚刚,Cursor 1.0炸裂发布!4大亮点实战
· SQL Server 2025 预览版新功能点评
· 理解 .NET 结构体字段的内存布局