Note - OI tricks | 动态更新
🌹:不知道这个是不是应该放到 Essay 里。其实这里更偏实现一点,不知道算不算 trick?
-
线段树维护 Hash。
这个左右区间是很好合并的,主要是提出来说一下,因为每次遇到都只会搜索。我指的是百度 / Google。
下面是一个维护正序和逆序 Hash 的板子。
using ull = unsigned long long; ull base[N]; struct segment_tree { int l, r; ull hash1, hash2; } t[N << 2]; void push_up(int p) { int mid = t[p].l + t[p].r >> 1; t[p].hash1 = (t[p << 1].hash1 * base[t[p].r - mid] + t[p << 1 | 1].hash1); t[p].hash2 = (t[p << 1 | 1].hash2 * base[mid - t[p].l + 1] + t[p << 1].hash2); } void build(int p, int l, int r) { t[p].l = l, t[p].r = r; if (l == r) { t[p].hash1 = t[p].hash2 = s[l] - 'a' + 1; return; } int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); push_up(p); } void update(int p, int x, char c) { if (t[p].l == t[p].r) { t[p].hash1 = t[p].hash2 = c - 'a' + 1; return; } int mid = t[p].l + t[p].r >> 1; if (x <= mid) update(p << 1, x, c); else update(p << 1 | 1, x, c); push_up(p); } ull ask1(int p, int l, int r) { if (l <= t[p].l && t[p].r <= r) return t[p].hash1; int mid = t[p].l + t[p].r >> 1; ull ans = 0; if (l <= mid) ans += ask1(p << 1, l, r) * base[max(min(r, t[p].r) - mid, 0)]; if (r > mid) ans += ask1(p << 1 | 1, l, r); return ans; } ull ask2(int p, int l, int r) { if (l <= t[p].l && t[p].r <= r) return t[p].hash2; int mid = t[p].l + t[p].r >> 1; ull ans = 0; if (l <= mid) ans += ask2(p << 1, l, r); if (r > mid) ans += ask2(p << 1 | 1, l, r) * base[max(mid - max(l, t[p].l) + 1, 0)]; return ans; }
-
bitset,\(O(n) \to O(\dfrac{n}{w})\) 的利器。
一般加速维护 bool 数组与起来、或起来等操作。 Solution - Walk the Runway
本日见到了线段树维护 bitset,可以跑过 \(O(\frac{mn \log n}{w})\),其中 \(n \leq 2 \times 10 ^ 4\),\(m \leq 10 ^ 3\)。
- 回退背包删除元素。
只需要反着跑一遍,做逆操作即可。 Solution - (subset sum = K) with Add and Erase。
-
莫队维护区间 mex。
小常数的 \(O(n \sqrt{n} \log n)\)。看代码秒懂:void add(int x, int k) { for (; x <= n; x += x & -x) bit[x] += k; } int ask(int x) { int sum = 0; for (; x; x -= x & -x) sum += bit[x]; return sum; } void add(int x) { if (!sum[a[x]]) add(a[x], 1); sum[a[x]]++; } void del(int x) { sum[a[x]]--; if (!sum[a[x]]) add(a[x], -1); }
然后判断就是
ask(q[i].id) == q[i].id - 1
。q[i].id
是值。哦哦,以及可以用回滚莫队维护,但是那玩意容易写错,所以就这样了。
-
高维前缀和。
其实很简单,设已知 \(f_S\),\(S \subset U\),要求出所有 \(g_{S} = \sum_{I \subset S} f_I\)。
初始 \(g_S = f_S\),然后转移枚举一下不取某元素 \(x \in S\) 的情况 \(g_S = \sum\limits_{x \in S} g_{S \setminus x}\),很好感性理解,这样加起来就是 \(S\) 的所有子集啦。时间复杂度 \(O(n \times 2 ^ n)\)。题目咕咕咕。
-
树状数组 \(O(\log n)\) 二分。
见远古博文。 -
对于和的平均值(形式化地,\(\bar a = \dfrac{\sum_{i = 1} ^ n a_i}{n}\)),可以转化成 \(a_i - \bar a\) 然后和 \(0\) 判断大小等。
-
对于第 \(k\) 大 / 小,转化为二分判断 \(\leq mid\) 或 \(\geq mid\) 的个数与 \(k\) 的大小比较。e.g. 中位数。
-
如果一个区间操作很有规律性,可以考虑转换差分。典型的比如加一个等差数列。
Posted by liuzimingc