关于一些卡空间小寄巧
ShiRoZeTsu · · 个人记录
众所周知,OI 是一个吝啬空间压榨时间的神奇竞赛,很多时候我们需要将空间尽可能控制在一个合理的范围内。于是各种优化空间的神仙操作就出现了。
所以我来稍微总结一下,有遗漏或者错误的内容换欢迎反馈。
真的会谢 qwq
bitset
bitset 可以用来代替 bool,而且这玩意儿空间奇小,常数更是
short
作为一个耳熟能详的数字:
有些时候,数字的值域会比较小,这个时候就可以用 short 去优化空间。可以直接将空间优化到原来的一半,效果还是十分显著的。
美中不足就是适用范围比较小,因为许多题的数据范围都是在
关于 short 的读入,输出,如果你使用 cin cout 就没问题,如果用 scanf printf 的话需要注意写法:
short x;
scanf("%hd", &x);
printf("%hd\n", x);
并不是很难记,而且就算没记住,考场上编译器也能检查出来的。
char
同 short,如果值域甚至小于 128,你甚至可以用 char 去存储。没关系,除了阴间亿点也没什么()
本人有幸写过()
位域 (bit-field)
我第一次接触这个东西是在这道题:
CF543E Listening to Music
这里面开主席树的时候结构体要占很大空间。我最初的写法是这样的:
struct node { int ls, rs, mx; } t[maxn*40];
结果当然是理所当然地 Memory Limited Exceeded on #1(悲
然后我翻开题解一看,嚯,大佬的写法秀了我一脸:
struct Node {
ull mx : 18, ls : 23, rs : 23;
} t[MAX_N * 40];
我:???怎么开 unsigned long long 比我 int 空间还小??
后来上网 bdfs 后了解到,这是一种名为位域的写法。
比如要写一个结构体,里面有三个元素,你写了三个 int:
struct node {
int x, y, z;
} ;
int main() {
node px;
printf("size of px is: %lu\n", sizeof(px)); return 0;
}
然后输出:
size of px is: 12
由于一个 int 占 4 个字节,三个 int 自然占 12 个字节。但是如果告诉你,这三个元素的都是
可以这么写:
struct node {
unsigned long long x : 18, y : 18, z : 18;
} ;
int main() {
node px;
printf("size of px is: %lu\n", sizeof(px)); return 0;
}
然后运行,输出:
size of px is: 8
非常神奇。正常一个 unsigned long long 占 8 个字节,也就是 16 位,值域可以表示到
- updated on 2024/3/5 15:57:md我sb了,一个 ull 占 64 位(
至于为什么不用 long long?因为 long long 有符号位,这样做避免了空间浪费。当然,如果实在需要用也没问题。
注意 bit 的分配。
- Updated on 2024/1/15 19:08
动态开点线段树 —— 标号存值
相信很多人可持久化线段树的修改与查询操作都是这么写的:
void modify(int& o, int l, int r, int pos, int val) {
if(!o) o = ++cnt;
t[o].val += val;
if(l == r) return;
if(pos <= mid) modify(t[o].ls, l, mid, pos, val);
else modify(t[o].rs, mid+1, r, pos, val);
}
int query(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) return t[o].val;
int res = 0;
if(ql <= mid) res += query(t[o].ls, l, mid, ql, qr);
if(mid < qr) res += query(t[o].rs, mid+1, r, ql, qr);
return res;
}
但是实际上,线段树的最后一层由于不需要再向下递归,也就是说没有儿子。这时候为叶子单独分一整个点出来实在不划算,我们考虑在这个位置只存值,不存儿子。
具体操作就是用点的标号来存储值:
void modify(int& o, int l, int r, int pos, int val) {
if(l == r) { o += val; return; }
if(!o) o = ++cnt;
t[o].val += val;
if(pos <= mid) modify(t[o].ls, l, mid, pos, val);
else modify(t[o].rs, mid+1, r, pos, val);
}
然后查询就加上特判即可:
int query(int o, int l, int r, int ql, int qr) {
if(l == r) return o;
if(ql <= l && r <= qr) return t[o].val;
int res = 0;
if(ql <= mid) res += query(t[o].ls, l, mid, ql, qr);
if(mid < qr) res += query(t[o].rs, mid+1, r, ql, qr);
return res;
}
循环代替递归
递归不光会带来较大的常数,由于需要存储栈帧,其也会为内存带来比较大的消耗。在内存不够时可以尽可能用循环来代替递归。
或者去把栈开大一点()
据说有一个大佬考场常数爆炸了,原因是使用了递归写法的快速幂。
我:啊?
数组重复利用
可能有些题需要开很多数组,然而某个数组可能在一次使用后就再也用不到了。这时可以用这个数组去存其他数组的值,正好废物利用。
有时候废物利用需要先清理垃圾。因此尽量选择不需要清理垃圾的数组,实在没有了再去清垃圾。尽量控制常数。
毕竟时间换空间什么的实在是很亏()