闲话 22.8.7
好这首歌我想(?)起来是什么了
是Phantom Spring
这里是歌词
幻影的春天,我们的春天
春風に靡く少女のように
如春风中摇曳的少女般
まるで穏やかな心だった
仿佛一颗毫无波澜的心
春風に靡く少女のように
好似随春风而飘动的少女一般
なぜわたしたちは光がなかったの
为什么我们失去了光芒的照耀
どこまでも伸びる黒い影は
望不到边际的漆黑之影
未来を見ている未来を見ている
看见了未来 看见了未来
ずっと待ってたんだよ
我一直在等着你哦
偽物の春を
伪造的春天
一度だけ、一度だけでいい
仅仅一次,一次就够了
わたし知ってたんだよ
我早就明白了哦
ゆきずりの影と歩いていく
与迎面走过的影子一同走下去
I'll just be forever a phantom.
我永远都将只是个幻影。
みんなより出来の悪い靴を
穿着比任何人都破的鞋
治しながらずっと歩いていた
一边治疗着一边从未停下脚步
産まれた時からその運命だった
我的命运从出生以来就如此
地面を見ている地面を見ている
望着地面 望着地面
ずっと待ってたんだよ
我一直都在等你哦
ありふれた日々を
那些平淡的日子
遅いけど、もう遅いけれど
即便很晚了,虽然已经太迟了
あなたを見ているあなた見ている
可我仍在看着你 仍在看着你
桜が泣いている
樱花哭泣着
わたしたちの春は
我们的春天
紛い物、紛い物だから
都是假的、都只是幻象啊
どうせ知ってたんだろう
反正你早就知道了吧
わたしあなたから逃げないよ
我不会从你身边逃走的哦
I'll just be forever a phantom.
我将作为幻影永存于此。
稍微看了一下 似乎雾霭相随的星界版本不是饭调的
而是香椎老师
《暴力 替换为 朴素地 替换全部》
一天130播放 我果然还是听歌时间太少
关于第二分块中的viv变量,我想提一个人名
懒
?为什么我上网搜到的东西全是哈希表
不一定是懒惰删除嘛 可以是懒惰思想
总而言之,懒惰操作的核心就是“当前执行不进行大操作,等没法不进行时统一进行”,通过平摊的思路优化复杂度。
线段树 lazy标记
尤见于区间修改操作。由于线段树维护信息的特殊性,其可以支持对整段区间的快速修改。因此,我们可以不进行所有子区间的修改,不必将信息下推到叶子节点,而是在修改区间完全覆盖的区间进行标记,随后直接返回。由于线段树每层最多贡献2个区间,树高为 \(O(\log n)\),因此懒惰修改的复杂度是正确的。
维护一段长为 \(n\) 的01序列, 支持区间翻转与区间查询1的个数.
考虑查询信息的情况。我们只需要记录1的个数,且每个位置非0即1,因此当区间翻转时只需用区间长度减去原1个数即可修改整段区间,因此考虑线段树lazy标记维护。
代码
struct SegmentBeats {
struct Interval {
int sum, lazy, siz;
#define ls (p<<1)
#define rs (p<<1|1)
#define sum(p) seg[p].sum
#define lazy(p) seg[p].lazy
#define siz(p) seg[p].siz
#define mid ((l+r)>>1)
} seg[N<<2];
void ps_p(int p) {
sum(p) = sum(ls) + sum(rs);
}
void ps_d(int p) {
if (lazy(p) == 0) return;
sum(ls) = siz(ls) - sum(ls);
sum(rs) = siz(rs) - sum(rs);
lazy(ls) ^= 1, lazy(rs) ^= 1;
lazy(p) = 0;
}
void build(int p, int l, int r) {
siz(p) = r - l + 1;
if (l == r) return;
build(ls, l, mid);
build(rs, mid+1, r);
}
void upd(int p, int l, int r, int L, int R) {
if (R < l or r < L) return;
if(L <= l and r <= R) {
sum(p) = siz(p) - sum(p);
lazy(p) ^= 1;
return ;
} ps_d(p);
if (L <= mid) upd(ls, l, mid, L, R);
if (R > mid) upd(rs, mid+1, r, L, R);
ps_p(p);
}
int qry(int p, int l, int r, int L, int R) {
if (R < l or r < L) return 0;
if(L <= l and r <= R) {
return sum(p);
} ps_d(p);
int ret = 0;
if (L <= mid) ret += qry(ls, l, mid, L, R);
if (R > mid) ret += qry(rs, mid+1, r, L, R);
return ret;
}
};
第二分块
核心操作:将区间内所有为 \(x\) 的元素改为 \(y\)。
这种时候我们发现线段树维护不了了,因为这玩意值域相关,而需要 \(O(n \log n)\) 个节点的线段树显然开不下。考虑分块。
我们将一个块内的相同信息合并。具体地,我们使用并查集维护每个位置的代表元,在合并时只需合并代表元即可。当需要修改散块时,朴素地查询每个位置的代表元,信息下传即可。
维护一段长为 \(n\) 的序列, 支持两种操作:
- 将 \([l,r]\) 区间内大于 \(x\) 的数字减去 \(x\)。
- 查询 \([l,r]\) 区间内等于 \(x\) 的数字的数量。
本题单值更新即本部分的核心操作。我们只需在序列上开并查集,在值域上统计信息,每个并查集点维护对应值,每个值域位置维护对应并查集内节点编号。
讨论单块更新。我们发现,第一个操作至多进行 \(O([值域])\) 次,因此平坦后朴素执行操作的复杂度正确。因此,我们可以每次记录单块最大值 \(\text{max}\),并朴素地将 \([x, \text{max}]\) 内的数字合并入 \([1, \text{max} - x]\) 内。然而这样的合并常数不是很优秀,我们需要进行启发式优化。具体地,我们查看 \(x\) 与 $\frac \max 2 $ 大小的关系,若 $x > \frac \max 2 $ 则朴素地合并 \([x, \text{max}]\),反之我们将区间 \([1, x]\) 并入 \([\max - x, \text{max}]\) 内,并打上区间减标记。
对于散块修改,可以朴素地将信息扫进序列内,再扫一遍对应区间进行修改,查询类似。
Welcome home, Chtholly
int v[MXIM], a[MXIM];
// v[i]是并查集里编号为i的节点的值,特用来存储根节点的值
struct block_info {
int mxim, lazy;
#define mx blk[block_id].mxim
#define lz blk[block_id].lazy
}blk[MXIM];
struct block_range {
int viv, num;
// g[i][j]是第i个块里j数的信息,viv是数所在位置在并查集里的根节点是哪个,num是出现次数
}g[405][MXIM];
int fa[MXIM];
inline int get(int x) {
return x == fa[x] ? x : fa[x] = get(fa[x]);
}
inline void ps_d (int block_id) {
// 把一个区间的信息下推到a数组上
register int e = ed(block_id);
rep(i,st(block_id), e){
a[i] = v[get(i)];
g[block_id][a[i]].viv = g[block_id][a[i]].num = 0;
a[i] -= lz;
} lz = 0;
rep(i, st(block_id), e) fa[i] = 0;
}
inline void ps_p (int block_id) {
// 和pushdown对应,把一个区间的信息上提到range数组里
mx = 0;
register int e = ed(block_id);
rep(i, st(block_id), e) {
mx = max(mx, a[i]), g[block_id][a[i]].num++;
if (g[block_id][a[i]].viv) fa[i] = g[block_id][a[i]].viv;
else v[i] = a[i], g[block_id][a[i]].viv = fa[i] = i;
}
}
inline void union_val(int block_id, int a, int b) {
// 把块id里的a拍进b里
if (g[block_id][b].viv) fa[g[block_id][a].viv] = g[block_id][b].viv;
else g[block_id][b].viv = g[block_id][a].viv, v[g[block_id][a].viv] = b;
g[block_id][b].num += g[block_id][a].num, g[block_id][a].num = g[block_id][a].viv = 0;
}
inline void tag(int block_id, int addin) {
if (addin <= mx - lz - addin) {
rep(i, lz + 1, lz + addin)
if (g[block_id][i].viv) union_val(block_id, i, i+addin);
lz += addin;
} else {
pre(i, mx, lz + addin + 1)
if (g[block_id][i].viv) union_val(block_id, i, i-addin);
mx = min(mx, lz + addin);
}
}
inline void change(int l, int r, int val) {
register int lf = bl(l), rt = bl(r);
ps_d(lf); if (lf != rt) ps_d(rt);
if (lf != rt) {
register int e = ed(lf);
rep(i, l, e) if (a[i] > val) a[i] -= val;
rep(i, st(rt), r) if (a[i] > val) a[i] -= val;
rep(i, lf + 1, rt - 1) tag(i, val);
ps_p(lf), ps_p(rt);
} else {
rep(i,l,r) if (a[i] > val) a[i] -= val;
ps_p(lf);
}
}
inline int qry(int l, int r, int val) {
register int lf = bl(l), rt = bl(r), ret = 0;
if (lf != rt) {
register int e = ed(lf);
rep(i, l, e) if (v[get(i)] - blk[lf].lazy == val) ret++;
rep(i, st(rt), r) if (v[get(i)] - blk[rt].lazy == val) ret++;
rep(i, lf + 1, rt - 1) if (val + blk[i].lazy <= 100000) ret += g[i][val + blk[i].lazy].num;
} else {
rep(i,l,r) if (v[get(i)] - blk[lf].lazy == val) ret++;
} return ret;
}
懒惰删除
常见于有序结构中。当你想删掉小顶堆内的最大值时,最好的方式就是别删。你可以标记一下堆内每个元素的存在性,当弹出已被删除的元素时需要不进行处理,再弹出下一个。如果结构大小需要进行比较的话,可以再加一个结构内被删除的元素的数量。
例子:对顶堆,堆优化dij,替罪羊树删除
差不多就这些,如果还有的话之后再写
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat220807.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。