Data Structure 2
不一定经典的 trick,但都比较简单,其中大部分是 Nityacke 做过的
线段树合并
Luogu P5384 雪松果树
给定一棵 \(n\) 个点的树,多次询问 \((u, k)\),求 \(u\) 节点 \(k\) 级祖先的 \(k\) 级儿子个数,\(1 \le n \le 10 ^ 6\)
其实完全 不需要用线段树合并(
这个题做法很多,放在这里作为线段树合并 板子 以及 \(O (n)\) 空间线段树合并的例子来讨论
思路是很简单的,第一次 DFS 的过程中 记录路径
从而将询问 \((u, k)\) 挂到 \(u\) 的 \(k\) 级祖先 \(u ^ k\) 上,于是变成求 \(u ^ k\) 的 \(k\) 级儿子个数的问题
线段树维护 每个点子树内 每种深度的点 有多少个,朴素做法需要 \(n\) 棵线段树
于是考虑 动态开点,并在第二次 DFS 中做 线段树合并
如果询问 强制在线,可以使用 可持久化线段树合并,即每次合并的时候 创建一个新的节点
这样就可以在合并时 保留合并前两树的信息,时空复杂度不变,但是 \(2\) 倍常数
考虑到 \(256 ~ MB\) 空间,上述做法显然 不能通过,于是考虑 \(O (n)\) 空间 线段树合并
正统的做法是 每次继承重儿子,然后暴力合并轻儿子,复杂度证明这里不赘述(可能是不会),形如
inline void dfs2 (const int32_t x, const int32_t f, const int32_t d = 1) {if (son[x]) dfs2 (son[x], x, d + 1);segtree::rt[x] = segtree::rt[son[x]];for (auto i : g[x])if (i != son[x])dfs2 (i, x, d + 1), segtree::merge (segtree::rt[x], segtree::rt[i]);segtree::insert (d, segtree::rt[x]);for (auto i : e[x])a[i] = segtree::query (d + w[i], segtree::rt[x]);
}
如果 随机继承一个儿子,那么可以被一个 形如毛毛虫 的形状卡掉。
注意到如果对于一个点,有 \(50 ~ \%\) 的概率 DFS 到叶子,\(50 ~ \%\) 的概率 仍在链上
那么到链底时期望 同时存在 的线段树就有 \(\dfrac {len} 2\),也就是 \(\dfrac n 4\) 棵,复杂度仍然是 \(O (n \log n)\),只是常数较小
如果需要使用 空间回收,那么请注意 回收站的大小,有时候仅开到 \(n\) 并不妥当
正确的代码
# include <bits/stdc++.h>const int32_t maxn = 1000005;const int32_t logn = 4;int32_t n, k, q;namespace segtree {struct node {int32_t ls, rs, val;} t[maxn * logn];# define lc (t[x].ls)# define rc (t[x].rs)# define m ((l + r) >> 1)int32_t root = 0;int32_t rt[maxn], cnt = 0;int32_t tr[maxn * logn], top = 0;inline void push (const int32_t x) {tr[++ top] = x, t[x].ls = t[x].rs = t[x].val = 0;}inline int32_t newnode () {return top ? tr[top --] : ++ cnt;}inline void maintain (const int32_t x) {t[x].val = t[lc].val + t[rc].val;}inline void insert (const int32_t v, int32_t & x = root, const int32_t l = 1, const int32_t r = n) {if (! x) x = newnode ();if (l == r) return ++ t[x].val, void ();v <= m ? insert (v, lc, l, m) : insert (v, rc, m + 1, r); maintain (x);}inline int32_t query (const int32_t v, const int32_t x, const int32_t l = 1, const int32_t r = n) {if (! x) return 0;if (l == r) return t[x].val;return v <= m ? query (v, lc, l, m) : query (v, rc, m + 1, r);}inline bool isleaf (const int32_t x) {return ! lc && ! rc;}inline void merge (const int32_t x, const int32_t y) {if (isleaf (x) && isleaf (y)) return t[x].val += t[y].val, push (y), void ();if (t[x].ls && t[y].ls) merge (t[x].ls, t[y].ls);else if (t[y].ls) t[x].ls = t[y].ls;if (t[x].rs && t[y].rs) merge (t[x].rs, t[y].rs);else if (t[y].rs) t[x].rs = t[y].rs;push (y), maintain (x);}}int32_t c[maxn], s[maxn], a[maxn], w[maxn];int32_t siz[maxn], son[maxn];std::basic_string <int32_t> p[maxn], e[maxn], g[maxn];inline void dfs1 (const int32_t x, const int32_t f) {s[++ k] = x, siz[x] = 1;for (auto i : p[x]) if (w[i] < k) e[s[k - w[i]]].push_back (i);for (auto i : g[x]) dfs1 (i, x), siz[x] += siz[i], siz[i] > siz[son[x]] ? son[x] = i : 0;-- k;
}inline void dfs2 (const int32_t x, const int32_t f, const int32_t d = 1) {if (son[x]) dfs2 (son[x], x, d + 1);segtree::rt[x] = segtree::rt[son[x]];for (auto i : g[x])if (i != son[x])dfs2 (i, x, d + 1), segtree::merge (segtree::rt[x], segtree::rt[i]);segtree::insert (d, segtree::rt[x]);for (auto i : e[x])a[i] = segtree::query (d + w[i], segtree::rt[x]);
}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q;for (int i = 2, f; i <= n; ++ i)std::cin >> f, g[f].push_back (i);for (int i = 1, u; i <= q; ++ i) std::cin >> u >> w[i], p[u].push_back (i);dfs1 (1, 0), dfs2 (1, 0);for (int i = 1; i <= q; ++ i)std::cout << (a[i] ? a[i] - 1 : 0) << ' ';return 0;
}
这个题的标算还是挺有意思的,考虑 将询问挂到对应 \(u ^ k\) 后,直接 DFS
然后记录 \(cnt\),并 进行差分,即在 进入 \(u ^ k\) 子树时减,退出 \(u ^ k\) 子树时加
可以做到时空 \(O (n)\)
在线的话等价于维护 区间某个颜色的数量,主席树可以时空 \(O (n \log n)\) 解决
一些会了这道题就应该会的题:
CF208E Blood Cousins
CF570D Tree Requests
Luogu P7581 「RdOI R2」路径权值 (distance)
CF1051G Distinctification
给定 \(n\) 个二元组 \((a _ i, b _ i)\),有两种操作
- \(a _ i = a _ i + 1\),代价 \(+ b _ i\),当仅当存在 \(a _ j = a _ i ~ (i \neq j)\) 时可以使用
- \(a _ i = a _ i - 1\),代价 \(- b _ i\),当仅当存在 \(a _ j = a _ i - 1 ~ (i \neq j)\) 时可以使用
对于每个 \(k\),求使得前 \(k\) 对二元组中 \(a _ i\) 两两不同的 最小代价
糖糖题,在 \(\color {red} \textsf N \color {black} \textsf {ityacke}\) 去年的 Segtree Tree 课件里面,但只有 \(O (1)\) 个 \(\color {red} \textsf z \color {black} \textsf {hicheng}\) 补了
考虑对于一个 确定的序列 怎么做,手玩一下可以知道 \(a _ i\) 能且只能在其 值域连续段 里 任意 活动
进而容易发现,如果我们先把一个值域连续段里的数 均先变为最小值,其代价为一个 定值
即 \(- \sum b _ i \times (a _ i - \min a _ i)\)
这个时候考虑将其变为 两两不同的数,可以证明,最优情况下,\(b _ i\) 越小,最终的 \(a _ i\) 越大
于是对 \(b _ i\) 排序,这一部分的权值即 \(\sum i \times b _ {p _ i}\)
其中 \(\{b _ {p _ 1}, b _ {p _ 2}, ..., b _ {p _ m}\}\) 是这个 \(a\) 的值域连续段中 \(b _ i\) 从大到小 排序后的结果
注意排序时 除去 连续段中 最小 \(a _ i\) 所对应的最大 \(b _ i\)
最终将 多个值域连续段 的代价 加起来 就可以了
回到这个题,怎么维护 前 \(k\) 对的答案,我们可以将其看作在前 \(k - 1\) 对的基础上加入第 \(k\) 对
也就是说,我们需要支持在 插入任意 \((a _ i, b _ i)\) 二元组的情况下维护答案,考虑维护的信息
-
\(a _ i\) 的 值域连续段
可以用 并查集 方便的维护 右端点,用一个数组记录 当前右端点对应的左端点 就行
-
连续段中全变成最小值的代价 \(- \sum b _ i \times (a _ i - \min a _ i)\)
考虑 加入一个 \((a _ i, b _ i)\),可能带来的影响无非是 合并两个段
改变右端点时加入对应 \(a _ i\) 即可,改变左端点时将对应段 整体左移(\(\min a _ i\) 改变)即可
需要记录连续段对应的 \(\sum b _ i\) 的值,转移是 \(O (1)\) 的
-
从大到小 排序的 \(b _ i\)
权值线段树 容易维护,这里就出现问题了,我们需要对每个 值域连续段 用一棵线段树
而加入一个二元组可能造成 连续段的合并,所以对应的,我们也要 线段树合并 来维护
-
\(\sum i \times b _ {p _ i}\)
同样在 权值线段树 下维护即可,记录 \(sum, siz, ans\) 三个信息,左右儿子合并 时即
t[x].ans = t[lc].ans + t[lc].sum * t[rc].siz + t[rc].ans; t[x].sum = t[lc].sum + t[rc].sum; t[x].siz = t[lc].siz + t[rc].siz;
做完了,细节主要在 连续段合并时的左右端点处理 上,不多
一种实现
# include <bits/stdc++.h>const int32_t maxn = 400005;const int32_t logn = 30;int32_t n;int64_t res = 0, tmp = 0;namespace segtree {struct node {int32_t ls, rs;int64_t sum, val, siz;} t[maxn * logn];# define lc (t[x].ls)# define rc (t[x].rs)# define m ((l + r) >> 1)int32_t root = 0;int32_t rt[maxn], cnt = 0;int32_t tr[maxn * logn], top = 0;inline int32_t newnode () {return top ? tr[top --] : ++ cnt;}inline void maintain (const int32_t x) {t[x].sum = t[lc].sum + t[lc].val * t[rc].siz + t[rc].sum;t[x].val = t[lc].val + t[rc].val;t[x].siz = t[lc].siz + t[rc].siz;}inline void ins (const int32_t v, int32_t & x = root, const int32_t l = 1, const int32_t r = n) {if (! x) x = newnode ();if (l == r) return t[x].sum = t[x].val = v, t[x].siz = 1, void ();v <= m ? ins (v, lc, l, m) : ins (v, rc, m + 1, r); maintain (x);}inline bool isleaf (const int32_t x) {return ! lc && ! rc;}inline int64_t query (const int32_t x) {return t[x].sum - t[x].val;}inline void merge (const int32_t x, const int32_t y) {if (t[x].ls && t[y].ls) merge (t[x].ls, t[y].ls);else if (t[y].ls) t[x].ls = t[y].ls;if (t[x].rs && t[y].rs) merge (t[x].rs, t[y].rs);else if (t[y].rs) t[x].rs = t[y].rs;tr[++ top] = y, t[y] = {0, 0, 0, 0, 0}, maintain (x);}inline void _merge (const int32_t x, const int32_t y) {tmp = tmp - query (x), tmp = tmp - query (y);merge (x, y);tmp = tmp + query (x);}}struct node {int32_t a, b;inline bool operator < (const node & x) const {return a == x.a ? b > x.b : a < x.a;}} p[maxn];int32_t f[maxn], lft[maxn];int64_t sum[maxn];inline int32_t F (const int32_t x) {return x == f[x] ? x : f[x] = F (f[x]);
}inline void init_base (const int32_t val, const int32_t now, const int64_t cst) {int32_t x = F (val);f[x] = F (x + 1), lft[f[x]] = lft[x];res = res - cst * (now - lft[x]);segtree::ins (cst, segtree::rt[x]);if (lft[x] != x) segtree::_merge (segtree::rt[lft[x]], segtree::rt[x]);if (F (x + 1) != x + 1)segtree::_merge (segtree::rt[lft[x]], segtree::rt[x + 1]);if (F (x + 1) != x + 1) res = res - sum[x + 1] * (x + 1 - lft[x]);sum[lft[x]] = sum[lft[x]] + sum[x + 1] + cst;}inline int64_t solve (const int32_t i) {int32_t r = p[i].a, x = F (r);init_base (x, p[i].a, p[i].b);return res + tmp;
}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n;for (int i = 1; i <= n; ++ i)std::cin >> p[i].a >> p[i].b;for (int i = 1; i <= 400000; ++ i)f[i] = i, lft[i] = i;for (int i = 1; i <= n; ++ i)std::cout << solve (i) << '\n';std::cerr << segtree::cnt << '\n';return 0;
}
Luogu P6847 [CEOI2019] Magic Tree
一棵树,点有 点权,选择一些点,使得所有被选择点对 \((u, v)\) 满足若 \(v\) 在 \(u\) 子树内,则必须有 \(d (u) > d (v)\)
求 选出的点最大权值和
注意题意不要理解错,那么容易得到 \(O (n ^ 2)\) 的暴力 DP
设 \(f _ {u, t}\) 表示 \(u\) 子树内均在 \(t\) 时刻及之前被选择(要断的边均在 \(t\) 时刻及之前断)时的 子树最大权值
枚举 \(t\),若 \(u\) 点不选,则 \(f _ {u, t} \leftarrow \sum _ {v \in son (u)} f _ {v, t}\),否则有 \(f _ {u, t} \leftarrow w (u) + \sum _ {v \in son (u)} f _ {v, d (u)} ~ (t \ge d (u))\)
上述操作本质就是将 儿子的 DP 值对位求和,然后将 \(d (u)\) 开始的 后缀 对 \(w (u) + f _ {v, d (u)} ~ (t \ge d (u))\) 取 \(\max\)
可以想到 线段树来维护 DP 值,而 儿子求和 这个操作引出了 线段树合并 的做法
这个题的 合并部分 是简单的
难点在于 线段树合并通常只支持 有交换律的运算,换句话说,我们要做 标记永久化
一种思路 是和 Segment Tree Beats 一样考虑 均摊,因为每次操作就是一个 推平
而所有操作至多创造出 \(O (n)\) 段 不同值的连续段,也就是说,暴力找到值相同的段 做 区间加 就行了
时间复杂度 \(O (n \log n)\),一段的值是否相同考虑 \(\min\) 和 \(\max\) 判等,这里给出一个这种思路的详细题解
还有一种 十分有趣的做法,也是笔者所采用的,我们分析 \(f _ u\) 的性质,容易发现其 单调不降
于是 后缀取 \(\max\) 其实就等价于 区间推平,并且这个区间是容易找到的
但是 区间赋值 标记 仍然不方便永久化,怎么办?
我们直接将这个区间完整包含的节点 全部删掉,然后给上一级区间 打上加法标记
也就等价于 全部推平成 \(0\) 后同时加上这个 \(\max\),由于 推平成 \(0\)(删除节点)这个操作 无需记录任何信息
自然就 不用考虑是否有交换律 的问题,于是相当于只剩下 加法标记,这是容易永久化的
为了找到所需要推平的区间,我们仍需区间 \(\min, \max\),找的过程类似 线段树二分,可以在 修改过程中顺便做
一种实现
# include <bits/stdc++.h>const int32_t maxn = 200005;const int32_t logn = 18;int32_t n, q, k;int32_t d[maxn], w[maxn], f[maxn];namespace segtree {struct node {int64_t tag, min, max;int32_t ls, rs;} t[maxn * logn];# define lc (t[x].ls)# define rc (t[x].rs)# define m ((l + r) >> 1)int32_t root = 0;int32_t rt[maxn], cnt = 0;int32_t tr[maxn * logn], top = 0;inline int32_t newnode () {return top ? tr[top --] : ++ cnt;}inline void push (const int32_t x) {if (! x) return ;tr[++ top] = x, t[x] = {0, 0, 0, 0, 0};}inline void maintain (const int32_t x) {t[x].min = std::min (t[lc].min, t[rc].min) + t[x].tag;t[x].max = std::max (t[lc].max, t[rc].max) + t[x].tag;}inline void modify (const int32_t L, const int64_t v, int32_t & x = root, const int32_t l = 1, const int32_t r = k) {if (! x) x = newnode ();if (L <= l) {if (t[x].max <= v)return push (lc), push (rc), t[x] = {v, v, v, 0, 0}, void ();else {if (t[lc].min + t[x].tag <= v)modify (L, v - t[x].tag, lc, l, m);if (t[rc].min + t[x].tag <= v)modify (L, v - t[x].tag, rc, m + 1, r);return maintain (x);}}L <= m ? modify (L, v - t[x].tag, lc, l, m) : void (); modify (L, v - t[x].tag, rc, m + 1, r), maintain (x);}inline bool isleaf (const int32_t x){return ! lc && ! rc;}inline void merge (const int32_t x, const int32_t y) {if (isleaf (x) && isleaf (y)) return t[x].tag += t[y].tag, t[x].min = t[x].max = t[x].tag, push (y);if (t[x].ls && t[y].ls) merge (t[x].ls, t[y].ls);else if (t[y].ls) t[x].ls = t[y].ls;if (t[x].rs && t[y].rs) merge (t[x].rs, t[y].rs);else if (t[y].rs) t[x].rs = t[y].rs;t[x].tag = t[x].tag + t[y].tag, push (y), maintain (x);}inline int64_t query (const int32_t p, const int32_t x = root, const int32_t l = 1, const int32_t r = k) {if (! x) return 0;if (l == r) return t[x].tag;return t[x].tag + (p <= m ? query (p, lc, l, m) : query (p, rc, m + 1, r));} }int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q >> k;for (int i = 2; i <= n; ++ i)std::cin >> f[i];for (int i = 1, nod, day, wei; i <= q; ++ i) std::cin >> nod >> day >> wei, d[nod] = day, w[nod] = wei;for (int i = n; i >= 2; -- i) {if (d[i]) segtree::modify (d[i], w[i] + segtree::query (d[i], segtree::rt[i]), segtree::rt[i]);if (! segtree::rt[f[i]]) segtree::rt[f[i]] = segtree::rt[i];else segtree::merge (segtree::rt[f[i]], segtree::rt[i]);}std::cout << segtree::t[segtree::rt[1]].max << '\n';return 0;
}
Segmant Tree Beats 的做法需要 均摊性质,而区间推平的做法依赖 权值不降 的性质,各有用处
练习 BZOJ 4399 魔法少女 LJJ,注意 Hydro 的数据范围是 不可靠的
2024.12.09 联考 BSZX T2 最小割
给定一个图,满足所有 不在给定生成树上的边 \((u, v)\),均满足 \(lca (u, v) = 1\)
定义一个割合法,当且仅当其 恰好包含两条 生成树上的边 \((u _ 1, v _ 1), (u _ 2, v _ 2)\)
且割完后 \(u _ 1\) 与 \(v _ 1\) 不连通,\(u _ 2\) 与 \(v _ 2\) 不连通
对于每条树边求出 包含这条边的最小割大小
你要先推一些东西,分两种情况
- 如果删去的两条 树边 没有祖先关系
那么显然能保留的 非树边 有且仅有 连接两条树边子树 的这些,其余的 都需要删掉
- 否则则可以证明 删去的两条边相邻 时不劣,对应答案是容易得到的
枚举(DFS)一条边,同时 DFS 得到另一条边在每个位置时 对应的答案,容易做到 \(O (n ^ 2)\)
此时用线段树维护 固定一条边,另一条边在不同位置时的答案,显然需要 全局最小值
初始所有权值均为 \(m - n + 1\),固定第一条边后,遍历其 所在子树 内的边,每条边对应贡献是一个 到根链减
但是这样的话我们就有 \(n\) 棵线段树,寄。
直接使用 动态开点线段树 + 树上启发式合并 可以空间 \(O (n)\) 时间 \(O (n \log ^ 3 n)\),通过!
但是我们有更好的做法,考虑直接 线段树合并,由于一共链减 \(O (m)\) 次,对应 \(O (m \log m)\) 个 dfn 区间
于是空间理论上是 \(O (n \log ^ 2 n)\) 的,而此时时间也是 \(O (n \log ^ 2 n)\) 的,可惜 空间比较劣
使用 \(O (n)\) 空间 线段树合并,即每次 先 dfs 重儿子并 直接继承重儿子的线段树
于是 一个重链只用一棵线段树,而 dfs 的过程最劣情况就是到 叶子,此时有用信息是一条 根到叶的链
这其中至多切换 \(O (\log n)\) 次 重链,也就说有 \(O (\log n)\) 棵 线段树,于是空间 \(O (n \log n)\),时间不变
注意做好 节点回收
实测 用 \(O (n \log n)\) 写法 最大同时使用节点数 只有 \(268965\) 个
但是用 \(O (n \log ^ 2 n)\) 写法 最大同时使用节点数 就有 \(8434374\) 个,Wow!
效率不错的实现
#include <bits/stdc++.h>const int32_t maxn = 50005;const int32_t logn = 6;const int32_t inf = 1e9;std::vector <uint16_t> g[maxn];
std::vector <uint16_t> e[maxn];int32_t n, q;struct edge {uint16_t u, v;
} eg[maxn];uint16_t mp[maxn];int32_t ans[maxn];namespace tree_cut {uint16_t siz[maxn], dfn[maxn], idf[maxn], top[maxn], son[maxn], fat[maxn];uint16_t cnt = 0;int32_t sbs[maxn];inline void dfs0 (const int32_t x, const int32_t f) {fat[x] = f;for (auto i : g[x]) if (i != f) dfs0 (i, x);}inline void dfs1 (const int32_t x, const int32_t f) {sbs[x] = e[x].size (), siz[x] = 1;for (auto i : g[x])if (i != f) dfs1 (i, x), siz[x] += siz[i], sbs[x] += sbs[i], siz[i] > siz[son[x]] ? son[x] = i : 0;if (x == 1) return ;for (auto i : g[x]) {if (i != f) {ans[mp[x]] = std::min (ans[mp[x]], 2 + sbs[x] - sbs[i]);ans[mp[i]] = std::min (ans[mp[i]], 2 + sbs[x] - sbs[i]);}}}inline void dfs2 (const int32_t x, const int32_t tp) {dfn[x] = ++ cnt, idf[cnt] = x, top[x] = tp;if (son[x]) dfs2 (son[x], tp);for (auto i : g[x])if (i != fat[x] && i != son[x])dfs2 (i, i); }}using namespace tree_cut;namespace segtree {struct node {int32_t ls, rs;int32_t min, tag;} t[maxn * logn];# define lc (t[x].ls)# define rc (t[x].rs)# define m ((l + r) >> 1)int32_t rt[maxn];int32_t tot = 0, tpm = 0, root = 0;std::stack <int32_t> s;inline int32_t newnode (const int32_t x = 0) {if (s.empty ())return t[++ tot] = t[x], t[tot].ls = t[tot].rs = t[tot].tag = 0, tot;elsereturn tpm = s.top (), t[tpm] = t[x], t[tpm].ls = t[tpm].rs = t[tpm].tag = 0, s.pop (), tpm;}inline void puttag (const int32_t x, const int32_t tag) {t[x].tag = t[x].tag + tag;}inline int32_t wow (const int32_t x, const int32_t y) {return x ? x : y;}inline void maintain (const int32_t x, const int32_t y) {t[x].min = std::min (t[wow (lc, t[y].ls)].min + t[wow (lc, t[y].ls)].tag, t[(wow (rc, t[y].rs))].min + t[(wow (rc, t[y].rs))].tag);}inline void build (const int32_t l = 1, const int32_t r = n, int32_t & x = root) {if (! x) x = newnode ();if (l == r) return t[x] = {0, 0, sbs[idf[l]], 0}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x, x);}inline void modify (const int32_t L, const int32_t R, const int32_t v, const int32_t l, const int32_t r, int32_t & x, int32_t & y) {if (L > r || l > R) return ;if (! x) x = newnode (y);if (L <= l && r <= R) return puttag (x, v);modify (L, R, v, l, m, t[x].ls, t[y].ls), modify (L, R, v, m + 1, r, t[x].rs, t[y].rs), maintain (x, y);}inline int32_t query (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return inf;if (L <= l && r <= R) return t[x].min + t[x].tag;return std::min (query (L, R, l, m, lc), query (L, R, m + 1, r, rc)) + t[x].tag;}inline bool isleaf (const int32_t x) {return (! lc && ! rc);}inline void merge (const int32_t x, const int32_t y, const int32_t fk = root) {if (x == y || ! x) return ;if (isleaf (x) && isleaf (y)) return t[x].min = std::min (t[x].min, t[y].min), t[x].tag = t[x].tag + t[y].tag, void ();if (t[x].ls && t[y].ls) merge (t[x].ls, t[y].ls, t[fk].ls), s.push (t[y].ls);else if (t[y].ls) t[x].ls = t[y].ls;if (t[x].rs && t[y].rs) merge (t[x].rs, t[y].rs, t[fk].rs), s.push (t[y].rs);else if (t[y].rs) t[x].rs = t[y].rs;maintain (x, fk);}inline void _merge (int32_t & x, int32_t & y) {if (! x) x = y;else merge (x, y);}}namespace operation {using namespace segtree;inline void minus (const int32_t x, const int32_t y, const int32_t v) {int32_t u = y;while (top[u] != 1) modify (dfn[top[u]], dfn[u], v, 1, n, rt[x], root), u = fat[top[u]];if (u != 1) modify (dfn[1] + 1, dfn[u], v, 1, n, rt[x], root), u = fat[top[u]];}inline void dfs (const int32_t x = 1) {if (son[x]) dfs (son[x]), rt[x] = rt[son[x]];for (auto i : g[x]) if (i != fat[x] && i != son[x]) dfs (i), _merge (rt[x], rt[i]);for (auto i : e[x]) minus (x, i, - 2);ans[mp[x]] = std::min (ans[mp[x]], std::min (query (1, dfn[x] - 1, 1, n, rt[x]), query (dfn[x] + siz[x], n, 1, n, rt[x])) + 2 + sbs[x]);}}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);memset (ans, 0x3f, sizeof ans);std::cin >> n >> q;for (int i = 1, u, v; i < n; ++ i) {std::cin >> u >> v, eg[i] = {(uint16_t) u, (uint16_t) v};g[u].push_back (v), g[v].push_back (u);}for (int i = n, u, v; i <= q; ++ i) {std::cin >> u >> v;e[u].push_back (v), e[v].push_back (u);}tree_cut::dfs0 (1, 0);for (int i = 1; i < n; ++ i) {if (eg[i].u == fat[eg[i].v]) mp[eg[i].v] = i;if (eg[i].v == fat[eg[i].u]) mp[eg[i].u] = i;}tree_cut::dfs1 (1, 0), tree_cut::dfs2 (1, 1);segtree::build (), operation::dfs ();for (int i = 1; i < n; ++ i)std::cout << ans[i] << ' ';return 0;
}
Segment Tree Beats
吉老师线段树,很好用啊(下文中的 区间最值操作 指 区间对 \(x\) 取 \(\min / \max\))
一些情况下也可以用 分块 在 不依赖均摊 的 情况下达到一样的效果,但是 复杂度更劣
其实更重要的是这种 均摊的思想,很多诡异的 DS 里面都可能涉及到
我先在这里丢个好东西,虽然里面的 有其他区间修改的 SegBeats 复杂度似乎有问题...
区间最值问题
HDU 5306 Gorgeous Sequence
区间对 \(x\) 取 \(\min\),求 区间 \(\max\),区间和,\(n \le 10 ^ 6\)
没有其他区间修改 的 Segment Tree Beats 板子
若 区间对 \(x\) 取 \(\min\) 与 区间对 \(x\) 取 \(\max\) 两种操作 同时存在,算作 有其他区间修改(两种 影响是分开的)
注意到 区间对一个数取 \(\max / \min\) 的操作其实就是 区间推平,在这个过程中 不同权值的段数期望是减少的
在没有其他区间修改的情况下,每次操作 至多增加 \(2\) 个这样的段(边界处)
换句话说,整个操作过程中,段数总和 是 \(O (m)\) 级别的,同时原始段数 \(O (n)\),故总段数 \(O (n + m)\)
只需要找到一种方法,可以在 较小时间 内 推平一个值相同段(减少一段),那么均摊后复杂度就是 可接受的
Segment Tree Beats 就是这样的一种方法,其可以在 \(O (\log n)\) 的时间 定位并推平一段
于是当 没有其他区间修改(总段数 \(O (n + m)\))时,用其可在 \(O ((n + m) \log n)\) 的时间解决问题(常数较大)
具体实现上,我们记录区间的 最大值 \(fmax\) 与 次大值 \(smax\),我们每次区间对 \(x\) 取 \(\min\) 时
只找到 \(smax \le x \le fmax\) 的子区间进行 赋值 \(x\),容易发现,这等价于 我们只改变了一个值相同段
考虑我们需要 额外维护哪些信息?
由于需要 区间和,故维护区间和 \(sum\),而考虑每次 赋值 给 \(sum\) 的改变?
每次赋值即将区间 所有 最大值 从 \(fmax\) 改为 \(x\),于是记录 最大值出现次数 \(cmax\) 即可方便维护
最后考虑 区间推平 这个信息需要一个 下传的懒标记,于是其 信息和标记完整了,合并是简单的
如果区间对 \(x\) 取 \(\max\),反过来维护 最小,次小值 及 最小值出现次数 即可
一种区间对 $x$ 取 $\min$ 的实现(本题)
# include <iostream>
# include <cstdint>const int32_t maxn = 1000005;const int32_t inf = 2147483647;int32_t n, q;int32_t a[maxn];inline int64_t min (const int64_t a, const int64_t b) {return a < b ? a : b;
}inline int64_t max (const int64_t a, const int64_t b) {return a > b ? a : b;
}namespace segtree {struct node {int64_t sum;int32_t fmax, smax, cmax, qmin;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].sum = t[lc].sum + t[rc].sum;t[x].fmax = max (t[lc].fmax, t[rc].fmax);t[x].smax = max (t[lc].smax, t[rc].smax);t[x].cmax = 0;t[x].cmax = t[x].cmax + (t[x].fmax == t[lc].fmax) * t[lc].cmax;t[x].cmax = t[x].cmax + (t[x].fmax == t[rc].fmax) * t[rc].cmax;if (t[lc].fmax < t[x].fmax)t[x].smax = max (t[x].smax, t[lc].fmax);if (t[rc].fmax < t[x].fmax)t[x].smax = max (t[x].smax, t[rc].fmax);}inline void putmin (const int32_t x, const int64_t tag) {if (t[x].fmax <= tag) return ;t[x].sum = t[x].sum + (tag - t[x].fmax) * t[x].cmax;t[x].fmax = tag;t[x].qmin = min (tag, t[x].qmin);}inline void update (const int32_t x) {if (t[x].qmin != + inf)putmin (lc, t[x].qmin), putmin (rc, t[x].qmin), t[x].qmin = + inf;}inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {t[x] = {0, - inf, - inf, 0, + inf};if (l == r) return t[x] = {a[l], a[l], - inf, 1, + inf}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].fmax <= v) return ;if (L <= l && r <= R && t[x].smax <= v) return putmin (x, v);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x); }inline int64_t que_max (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].fmax;update (x); return max (que_max (L, R, l, m, lc), que_max (L, R, m + 1, r, rc));}inline int64_t que_sum (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return 0;if (L <= l && r <= R) return t[x].sum;update (x); return que_sum (L, R, l, m, lc) + que_sum (L, R, m + 1, r, rc);}}inline void solve (const int32_t Case) {std::cin >> n >> q;for (int i = 1; i <= n; ++ i)std::cin >> a[i];segtree::build ();for (int i = 1, opt, l, r, x; i <= q; ++ i) {std::cin >> opt;switch (opt) {case 0 : std::cin >> l >> r >> x, segtree::opt_min (l, r, x); break;case 1 : std::cin >> l >> r, std::cout << segtree::que_max (l, r) << '\n'; break;case 2 : std::cin >> l >> r, std::cout << segtree::que_sum (l, r) << '\n'; break;}}}int32_t T;int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> T;for (int i = 1; i <= T; ++ i) solve (i);return 0;
}
附 `datamaker.cpp`
# include <bits/stdc++.h>std::mt19937 rd (std::random_device {} ());inline int64_t rnd (const uint32_t l = 1, const uint32_t r = - 1) {return rd () % (r - l + 1) + l;
}int32_t n;int32_t q;const int32_t N = 100000;
const int32_t V = 2000000000;inline void solve (const int32_t Case) {n = rnd (N >> 1, N), q = rnd (N >> 1, N);std::cout << n << ' ' << q << '\n';for (int i = 1; i <= n; ++ i)std::cout << rnd (1, V) << ' ';std::cout << '\n';for (int i = 1, opt, l, r, x; i <= q; ++ i) {opt = rnd (0, 2), l = rnd (1, n), r = rnd (1, n), x = rnd (1, V);if (l > r) std::swap (l, r);if (opt)std::cout << opt << ' ' << l << ' ' << r << '\n';elsestd::cout << opt << ' ' << l << ' ' << r << ' ' << x << '\n';}}int32_t T;int main () {// std::cin >> T;T = rnd (1, 20);std::cout << T << '\n';for (int i = 1; i <= T; ++ i) solve (i);return 0;
}
Luogu P10639 最假女选手 (BZOJ4695)
区间对 \(x\) 取 \(\min / \max\),区间加,求 区间 \(\min / \max\),区间和,\(n \le 5 \times 10 ^ 5\)
有其他区间修改 的 Segment Tree Beats 板子,小清新啊
提供一种比较感性的复杂度分析,考虑一次 其他区间操作 对一个 区间最值操作 带来的影响
通过上一题发现,区间最值操作 时间与形如 \(smax \le x < fmax\) 的 节点数 有关,每一个需要 \(O (\log)\) 时间处理
而一次 其他区间操作,影响 \(O (\log)\) 个线段树区间,同时也至多新多出 \(O (\log)\) 这样的节点
\(smax, fmax\) 只有路径上的 \(O (\log)\) 个节点有可能改变,故其与 \(x\) 的关系也只有 \(O (\log)\) 个节点有可能改变
那么总共新建的这样节点个数就是 \(O (m \log n)\) 的,故时间复杂度 \(O (m \log ^ 2 n)\)(常数还好)
虽然 不带其他区间修改的 SegtBeats 常数较大
但由于 一次其他区间操作 很难改变满 \(O (\log)\) 个节点的 \(fmax, x, smax\) 大小关系
故其实 带其他区间修改的 SegtBeats 常数 反而不大
可以从这两道题的 实际运行结果看出端倪
具体实现上,我们只需要比上一道题多考虑三种东西:
-
区间最值操作 之间 标记的 复合
对于影响到的区间,其类似 区间赋值标记,故下传时直接 对儿子标记取 \(\min / \max\) 即可
-
区间加 标记与 区间最值操作标记 之间的复合
同样的将 区间最值操作 当作 区间赋值 来考虑,于是下传时 将 儿子区间最值标记 加 父亲区间加标记值
-
标记下传顺序
先加再覆盖,同理,先 区间加标记 再 区间最值操作标记
这里的 标记下传顺序,先加再覆盖和先覆盖再加可能都可以?有没有人是反过来的?
然后直接开写就行了,需要调试可以使用上一题的 datamaker.cpp
简单修改
一种可以过的代码
# include <bits/stdc++.h>const int32_t maxn = 500005;const int32_t inf = 1145141919;int32_t n, q;int32_t a[maxn];namespace segtree_beats {struct node {int64_t sum, add, len;int64_t fmin, fmax, smin, smax, cmax, cmin, qmin, qmax;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline node operator + (const node & a, const node & b) {node c;c.cmin = c.cmax = 0;c.fmax = c.qmax = - inf;c.fmin = c.qmin = + inf;c.sum = a.sum + b.sum;c.len = a.len + b.len;c.fmin = std::min (a.fmin, b.fmin);c.smin = std::min (a.smin, b.smin);c.fmax = std::max (a.fmax, b.fmax);c.smax = std::max (a.smax, b.smax);c.cmin += (a.fmin == c.fmin) * a.cmin;c.cmin += (b.fmin == c.fmin) * b.cmin;c.cmax += (a.fmax == c.fmax) * a.cmax;c.cmax += (b.fmax == c.fmax) * b.cmax;if (a.fmax < c.fmax)c.smax = std::max (a.fmax, c.smax);if (b.fmax < c.fmax)c.smax = std::max (b.fmax, c.smax); if (a.fmin > c.fmin)c.smin = std::min (a.fmin, c.smin);if (b.fmin > c.fmin)c.smin = std::min (b.fmin, c.smin);return c;} inline void maintain (const int32_t x) {t[x] = t[lc] + t[rc];}inline void putmax (const int32_t x, const int64_t tag) {if (t[x].fmin > tag) return ;t[x].sum = t[x].sum + (tag - t[x].fmin) * t[x].cmin;if (t[x].fmax == t[x].fmin) t[x].fmax = tag;if (t[x].smax == t[x].fmin) t[x].smax = tag;t[x].qmin = std::max (t[x].qmin, tag);t[x].qmax = std::max (t[x].qmax, tag);t[x].fmin = tag;}inline void putmin (const int32_t x, const int64_t tag) {if (t[x].fmax < tag) return ;t[x].sum = t[x].sum + (tag - t[x].fmax) * t[x].cmax;if (t[x].fmin == t[x].fmax) t[x].fmin = tag;if (t[x].smin == t[x].fmax) t[x].smin = tag;t[x].qmin = std::min (t[x].qmin, tag);t[x].qmax = std::min (t[x].qmax, tag);t[x].fmax = tag;}inline void putadd (const int32_t x, const int64_t tag) {t[x].sum = t[x].sum + tag * t[x].len;t[x].add += tag;t[x].fmin += tag, t[x].fmax += tag;if (t[x].smin != + inf) t[x].smin += tag;if (t[x].smax != - inf) t[x].smax += tag;if (t[x].qmin != + inf) t[x].qmin += tag;if (t[x].qmax != - inf) t[x].qmax += tag;}inline void update (const int32_t x) {if (t[x].add)putadd (lc, t[x].add), putadd (rc, t[x].add), t[x].add = 0;if (t[x].qmin != + inf)putmin (lc, t[x].qmin), putmin (rc, t[x].qmin), t[x].qmin = + inf;if (t[x].qmax != - inf)putmax (lc, t[x].qmax), putmax (rc, t[x].qmax), t[x].qmax = - inf; }inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {t[x].qmin = + inf, t[x].qmax = - inf;if (l == r)return t[x] = {a[l], 0, 1, a[l], a[l], + inf, - inf, 1, 1, + inf, - inf}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_add (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R) return putadd (x, v);update (x), opt_add (L, R, v, l, m, lc), opt_add (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_max (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].fmin >= v) return ;if (L <= l && r <= R && t[x].smin >= v) return putmax (x, v);update (x), opt_max (L, R, v, l, m, lc), opt_max (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].fmax <= v) return ;if (L <= l && r <= R && t[x].smax <= v) return putmin (x, v);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x); }inline int64_t que_sum (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return 0;if (L <= l && r <= R) return t[x].sum;update (x); return que_sum (L, R, l, m, lc) + que_sum (L, R, m + 1, r, rc);}inline int64_t que_min (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return + inf;if (L <= l && r <= R) return t[x].fmin;update (x); return std::min (que_min (L, R, l, m, lc), que_min (L, R, m + 1, r, rc));}inline int64_t que_max (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].fmax;update (x); return std::max (que_max (L, R, l, m, lc), que_max (L, R, m + 1, r, rc));}}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n;for (int i = 1; i <= n; ++ i)std::cin >> a[i];segtree_beats::build (), std::cin >> q;for (int i = 1, opt, l, r, x; i <= q; ++ i) {std::cin >> opt;switch (opt) {case 1 : std::cin >> l >> r >> x, segtree_beats::opt_add (l, r, x); break ;case 2 :std::cin >> l >> r >> x, segtree_beats::opt_max (l, r, x); break ;case 3 : std::cin >> l >> r >> x, segtree_beats::opt_min (l, r, x); break ;case 4 : std::cin >> l >> r, std::cout << segtree_beats::que_sum (l, r) << '\n'; break ;case 5 : std::cin >> l >> r, std::cout << segtree_beats::que_max (l, r) << '\n'; break ;case 6 : std::cin >> l >> r, std::cout << segtree_beats::que_min (l, r) << '\n'; break ;}}return 0;
}
吉老师的原论文中还有 几个 只涉及 区间最值操作 的原创例题,由于没有提交途径,就不放在这里了
都是一些小 \(trick\) 的复合,在 trsins 老师的某篇博客中也有对那 几道 题的讲解
CF1290E Cartesian Tree
给定 \(1 \sim n\) 的排列
对于所有 \(k \in [1, n]\),求 只考虑 \(\le n\) 的数的数列 的 笛卡尔树 上 每个点的 子树大小和
比较简单的应用,仍然先考虑 静态问题,也就是假设给定序列,怎么求 子树大小和
可以发现,一个数对应的点的 子树 就是在数列上 前后第一个比它大的数 之间的数 对应的点
设 \(i\) 位置下一个比它大的数为 \(nxt _ i\),上一个为 \(pre _ i\),那么答案就是 \(\sum nxt _ i - pre _ i + 1\)
容易发现 \(\sum nxt _ i\) 与 \(\sum pre _ i\) 较为对称,只考虑求 \(\sum nxt _ i\) 即可(\(pre _ i\) 反过来再求一遍
这时候考虑 插入一个数 带来的影响,可以发现每次插入的数均为 最大值,设插在位置 \(p\)
显然,位置在 \(1 \sim p - 1\) 的数 的 \(nxt\) 不可能大于 \(p\),换言之,它们的 \(nxt\) 要对 \(p\) 取 \(\min\)
对于位置在 \(p + 1 \sim n\) 的数,由于前面 插入一个数,故 位置 需要 \(+ 1\),对应 \(nxt + 1\)
于是得到了动态维护 \(nxt\) 所需要的操作:区间加,区间对 \(x\) 取 \(\min\),单点修改,区间和
使用 Segment Tree Beats 即可方便维护,对于这个题,由于需要每次 加点
所以直接维护 极值 / 其余值 个数,极值 / 其余值 增量 会比模板题 区间覆盖 的写法好一些
注意,需要在 下传标记 时讨论 最大值位置 并 分别处理
复杂度,显然这是 有其他区间操作的情况,故为 \(O (m \log ^ 2 n)\),\(m\) 指 操作次数
一种实现
# include <bits/stdc++.h>const int32_t maxn = 150005;const int32_t inf = 1000000000;int32_t n;int32_t a[maxn], p[maxn];namespace segtree {struct node {int64_t sum, add1, add2;int64_t fmax, smax;int64_t cmax, coth;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].sum = t[lc].sum + t[rc].sum;t[x].fmax = std::max (t[lc].fmax, t[rc].fmax);t[x].smax = std::max (t[lc].smax, t[rc].smax);t[x].add1 = t[x].add2 = t[x].cmax = t[x].coth = 0;if (t[lc].fmax < t[x].fmax)t[x].smax = std::max (t[x].smax, t[lc].fmax);if (t[rc].fmax < t[x].fmax)t[x].smax = std::max (t[x].smax, t[rc].fmax);t[x].cmax = t[x].cmax + (t[lc].fmax == t[x].fmax) * t[lc].cmax;t[x].cmax = t[x].cmax + (t[rc].fmax == t[x].fmax) * t[rc].cmax;t[x].coth = t[x].coth + t[lc].coth + (t[lc].fmax != t[x].fmax) * t[lc].cmax;t[x].coth = t[x].coth + t[rc].coth + (t[rc].fmax != t[x].fmax) * t[rc].cmax;}inline void puttag (const int32_t x, const int64_t add1, const int64_t add2) {if (t[x].coth == 0 && t[x].cmax == 0) return ;t[x].sum = t[x].sum + t[x].coth * add1 + t[x].cmax * add2;if (t[x].fmax != - inf) t[x].fmax = t[x].fmax + add2;if (t[x].smax != - inf) t[x].smax = t[x].smax + add1;if (t[x].coth) t[x].add1 = t[x].add1 + add1;if (t[x].cmax) t[x].add2 = t[x].add2 + add2;}inline void update (const int32_t x) {if (t[x].add1 == 0 && t[x].add2 == 0) return ;int64_t fmax = std::max (t[lc].fmax, t[rc].fmax);if (t[lc].fmax == fmax)puttag (lc, t[x].add1, t[x].add2);else puttag (lc, t[x].add1, t[x].add1);if (t[rc].fmax == fmax)puttag (rc, t[x].add1, t[x].add2);elseputtag (rc, t[x].add1, t[x].add1);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].fmax <= v || t[x].cmax + t[x].coth == 0) return ;if (L <= l && r <= R && t[x].smax <= v) return puttag (x, 0, v - t[x].fmax);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_add (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].cmax + t[x].coth == 0) return ;if (L <= l && r <= R) return puttag (x, v, v);update (x), opt_add (L, R, v, l, m, lc), opt_add (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_mod (const int32_t p, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (l == r) return t[x] = {v, 0, 0, v, - inf, 1, 0}, void ();update (x), p <= m ? opt_mod (p, v, l, m, lc) : opt_mod (p, v, m + 1, r, rc); maintain (x);}inline int64_t que_cnt (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].cmax + t[x].coth == 0) return 0;if (L <= l && r <= R) return t[x].coth + t[x].cmax;update (x); return que_cnt (L, R, l, m, lc) + que_cnt (L, R, m + 1, r, rc);} inline int64_t que_sum () {return t[1].sum;}}int64_t ans[maxn];int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n;for (int i = 1; i <= n; ++ i)std::cin >> a[i], p[a[i]] = i;for (int i = 1, c = 0; i <= n; ++ i) {segtree::opt_mod (p[i], i + 1);c = segtree::que_cnt (1, p[i]);if (p[i] + 1 <= n) segtree::opt_add (p[i] + 1, n, 1);if (p[i] - 1 >= 1) segtree::opt_min (1, p[i] - 1, c);ans[i] = segtree::que_sum ();}memset (segtree::t, 0, sizeof segtree::t);for (int i = 1; i <= n; ++ i)p[i] = n - p[i] + 1;for (int i = 1, c = 0; i <= n; ++ i) {segtree::opt_mod (p[i], i + 1);c = segtree::que_cnt (1, p[i]);if (p[i] + 1 <= n) segtree::opt_add (p[i] + 1, n, 1);if (p[i] - 1 >= 1) segtree::opt_min (1, p[i] - 1, c);ans[i] = ans[i] - 1ll * i * (i + 1) + segtree::que_sum () - i;}for (int i = 1; i <= n; ++ i)std::cout << ans[i] << '\n';return 0;
}
2024.12.06 联考 HSEFZ T2 第三题
给定 \(n\) 个集合 \(S _ i\),其中第 \(i\) 个集合包含了 \([l _ i, r _ i]\) 内的所有整数
进行 \(m\) 次询问,每次给定 \([l, r]\),你需要求出所有被 \([l, r]\) 包含的集合的并集大小
是个 Segment Tree Beats 套 树状数组 的题
我和 🚗 场上看错了两种不同的题意,使得 \(0 ~ pt\),草草草
考虑 值域扫描线,当然先 离散化,给 每个点(值域上)放一个权值,即 到后面点的距离
从左向右扫右端点,我们考虑记录每个值域点在 左端点至少多少 的时候可以被覆盖
于是加入一个 条件区间 \([l, r]\) 等价于给 这个区间 值域 对 \(l\) 取 \(\max\)
一个询问 \([l, r]\),即找 \([l, r]\) 值域区间内 上面维护的值 大于等于 \(l\) 的点的 权值和
用 Segtree Beats 套一个 树状数组 就可以 较为简单的解决
显然 没有其他的区间操作,于是前面 Segment Tree Beats 是 \(O (n \log n)\) 的
然后树状数组有一个 \(\log\),故总时间复杂度 \(O (n \log ^ 2 n)\)
注意每次只在 大区间被修改时修改树状数组,下传标记到小区间时 不动树状数组
否则虽然正确性大致没有问题,但是时间上会多一个 \(\log\)
效率不错的实现
# include <bits/stdc++.h>const int32_t maxn = 400005;const int32_t maxq = 1000005;int32_t n, q, k;namespace bit {int64_t t[maxn << 1];# define lowbit(x) (+ x & - x)inline void add (const int32_t p, const int64_t v) {for (int x = p; x >= 1; x = x - lowbit (x))t[x] = t[x] + v;}inline int64_t qsum (const int32_t p) {int64_t res = 0;for (int x = p; x <= k; x = x + lowbit (x))res = res + t[x];return res;}}int32_t a[maxn];namespace segtree {struct node {int32_t fmn, smn, sum, tag;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline node operator + (const node & a, const node & b) {node c = {std::min (a.fmn, b.fmn), std::min (a.smn, b.smn), 0, 0};if (a.fmn == c.fmn)c.sum = c.sum + a.sum;else c.smn = std::min (c.smn, a.fmn);if (b.fmn == c.fmn)c.sum = c.sum + b.sum;elsec.smn = std::min (c.smn, b.fmn);return c;}inline void puttag (const int32_t x, const int32_t tag, const int32_t type = 0) {if (type == 1) bit::add (t[x].fmn, - t[x].sum);t[x].fmn = t[x].tag = tag;if (type == 1) bit::add (t[x].fmn, + t[x].sum);}inline void update (const int32_t x) {if (t[x].tag) {if (t[lc].fmn <= t[rc].fmn) puttag (lc, t[x].tag);if (t[rc].fmn <= t[lc].fmn) puttag (rc, t[x].tag);t[x].tag = 0;}}inline void build (const int32_t l = 1, const int32_t r = k - 1, const int32_t x = 1) {if (l == r) return t[x] = {0, k + 1, a[r + 1] - a[l], 0}, void ();build (l, m, lc), build (m + 1, r, rc), t[x] = t[lc] + t[rc];}inline void modify (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = k - 1, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R) {if (v <= t[x].fmn) return ;if (t[x].fmn <= v && v < t[x].smn) return puttag (x, v, 1);}update (x), modify (L, R, v, l, m, lc), modify (L, R, v, m + 1, r, rc), t[x] = t[lc] + t[rc];}}struct interval {int32_t l, r;
} p[maxn], w[maxq];std::vector <int32_t> inv[maxn], que[maxn];int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q;for (int i = 1; i <= n; ++ i) {std::cin >> p[i].l >> p[i].r, ++ p[i].r;a[++ k] = p[i].l, a[++ k] = p[i].r;}std::sort (a + 1, a + k + 1);k = std::unique (a + 1, a + k + 1) - a - 1;for (int i = 1; i <= n; ++ i) {p[i].l = std::lower_bound (a + 1, a + k + 1, p[i].l) - a;p[i].r = std::lower_bound (a + 1, a + k + 1, p[i].r) - a - 1;}for (int i = 1, l, r; i <= q; ++ i) {std::cin >> l >> r, ++ r;w[i].l = std::lower_bound (a + 1, a + k + 1, l) - a;w[i].r = std::lower_bound (a + 1, a + k + 1, r) - a;w[i].r -= (a[w[i].r] != r) + 1; }for (int i = 1; i <= n; ++ i) inv[p[i].r].push_back (p[i].l);for (int i = 1; i <= q; ++ i) que[w[i].r].push_back (w[i].l);segtree::build ();int64_t ans = 0;for (int i = 1; i <= k; ++ i) {for (auto x : inv[i]) segtree::modify (x, i, x); for (auto x : que[i]) ans = ans ^ bit::qsum (x);}std::cout << ans << '\n';return 0;
}
UOJ #515. 【UR #19】前进四
给定序列,单点修改,单点查询 后缀最小值的不同值个数
结合 换维扫描线,可以自行思考
注意到我们需要 后缀信息,所以可以考虑操作 序列维
更进一步的,考虑 从后向前 扫描序列,先考虑 直接维护每个点答案是否能做
容易发现一个点会在 不同时间查询多次,直接维护点 会倒闭
这启发我们维护 时间轴,这时候考察 修改的贡献,其使得 一个数划分成了多个时间段
其中 每个时间段值是一样的,且总段数 \(O (m)\),设位置 \(p\) 存在一个值为 \(v\) 的时间段 \((l, r)\)
可以发现,当扫到 \(p\) 时,\((l, r)\) 时刻 大于 \(v\) 的数 都会使得这个 \(v\) 产生贡献
即在 \((l, r)\) 时刻,从 \(p + 1\) 开始的 后缀最小值 大于 \(v\),故此时若 \(a _ p = v\),则有贡献
换言之,我们将 \((l, r)\) 对 \(v\) 取 \(\min\),影响到的数 就是有贡献的
于是我们需要支持 区间对 \(x\) 取 \(\min\),单点查询 这个点被 多少次取 \(\min\) 操作影响
只需要维护 最大值,次大值,最大值增量,影响位置增量 即可,时间复杂度 \(O (m \log n)\)
这个都写不出来?♡ 真是弱哎 ♡ ~
# include <bits/stdc++.h>const int32_t maxn = 1000005;const int32_t inf = 1e9;int32_t n, q;int32_t a[maxn];namespace segtree {struct node {int32_t fst_max, sec_max;int32_t add_max, add_tag;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].fst_max = std::max (t[lc].fst_max, t[rc].fst_max);t[x].sec_max = std::max (t[lc].sec_max, t[rc].sec_max);if (t[lc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[lc].fst_max);if (t[rc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[rc].fst_max);}inline void puttag (const int32_t x, const int32_t add_max, const int32_t add_tag) {t[x].add_tag = t[x].add_tag + add_tag;t[x].fst_max = t[x].fst_max + add_max;t[x].add_max = t[x].add_max + add_max;}inline void update (const int32_t x) {if (t[x].add_max || t[x].add_tag) {int32_t fmax = std::max (t[lc].fst_max, t[rc].fst_max);if (t[lc].fst_max == fmax) puttag (lc, t[x].add_max, t[x].add_tag);if (t[rc].fst_max == fmax)puttag (rc, t[x].add_max, t[x].add_tag);t[x].add_max = t[x].add_tag = 0;}}inline void build (const int32_t l = 0, const int32_t r = q, const int32_t x = 1) {if (l == r) return t[x] = {+ inf, - inf, 0, 0}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 0, const int32_t r = q, const int32_t x = 1) {if (L > r || l > R || t[x].fst_max <= v) return ;if (L <= l && r <= R && t[x].sec_max <= v) return puttag (x, v - t[x].fst_max, 1);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x);}inline int64_t que_sum (const int32_t p, const int32_t l = 0, const int32_t r = q, const int32_t x = 1) {if (l == r) return t[x].add_tag;update (x); return p <= m ? que_sum (p, l, m, lc) : que_sum (p, m + 1, r, rc);}
}struct ques {int32_t x, id;
};std::vector <ques> p[maxn];
std::vector <int32_t> w[maxn];int32_t ans[maxn];int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q;for (int i = 1; i <= n; ++ i)std::cin >> a[i];segtree::build ();for (int i = 1, opt, x, y; i <= q; ++ i) {std::cin >> opt >> x;if (opt == 1) std::cin >> y, p[x].push_back ({y, i});if (opt == 2) w[x].push_back (i);}for (int i = n, l = 0, r = 0, lst; i >= 1; -- i) {l = 0, lst = a[i];for (auto x : p[i])r = x.id, segtree::opt_min (l, r - 1, lst), l = r, lst = x.x;segtree::opt_min (l, q, lst);for (auto x : w[i])ans[x] = segtree::que_sum (x);} for (int i = 1; i <= q; ++ i)if (ans[i])std::cout << ans[i] << '\n';return 0;
}
历史最值问题
可能绝大多数人都学过,但是不熟悉的话可以借此复习一下?
真的的会有人想复习这种东西?
我是史学家,这就是史
Luogu P4314 CPU 监控
给定序列,支持区间加,区间覆盖,维护 区间 \(\max\),区间历史 \(\max\)
再过两年这东西不会 降蓝 吧
没有区间最值操作 的 历史最值板子,仍然先考虑需要 哪些信息和标记
信息可以只需要 区间最大值 和 区间历史最大值 即可
标记至少也有 区间加 和 区间覆盖标记,但这 显然不够
容易发现,如果整体先加一个正数,再加一个负数,此时 只维护区间加 就会将操作 合并
但是显然这样合并对于历史最大值 是不优的,其会在 加正数的时候 取到最大
这启发我们维护 区间加标记的历史最大值,同理会有 区间覆盖标记的历史最大值
剩下唯一的问题是 标记的复合,感觉可以直接做?但有一些 细节
注意到 两个历史标记 实际上是指的 上次下传之后的历史最大值,故我们是需要下传清空的
下传顺序 先加再覆盖,若下传加法标记时此时 儿子覆盖标记有值,则直接 加在标记上
也就是说,我们将覆盖操作之后的所有操作 都视作覆盖操作
时间复杂度(有其他区间操作的 Segment Tree Beats)\(O (m \log ^ 2 n)\)
直接看代码会好理解很多
# include <bits/stdc++.h>const int32_t maxn = 100005;const int64_t inf = 1145141919810;int32_t n, q;int32_t a[maxn];namespace segtree {struct node {int64_t now_max, his_max;int64_t now_add, now_cov;int64_t max_add, max_cov;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].now_add = t[x].max_add = 0;t[x].now_cov = t[x].max_cov = - inf;t[x].now_max = std::max (t[lc].now_max, t[rc].now_max);t[x].his_max = std::max (t[lc].his_max, t[rc].his_max);}inline void putcov (const int32_t x, const int64_t now_tag, const int64_t max_tag) {t[x].now_max = t[x].now_cov = now_tag;t[x].his_max = std::max (t[x].his_max, max_tag);t[x].max_cov = std::max (t[x].max_cov, max_tag);}inline void putadd (const int32_t x, const int64_t now_tag, const int64_t max_tag) {if (t[x].now_cov != - inf) {t[x].max_cov = std::max (t[x].max_cov, t[x].now_cov + max_tag);t[x].his_max = std::max (t[x].his_max, t[x].now_max + max_tag); t[x].now_cov = t[x].now_cov + now_tag;t[x].now_max = t[x].now_max + now_tag;} else {t[x].max_add = std::max (t[x].max_add, t[x].now_add + max_tag);t[x].his_max = std::max (t[x].his_max, t[x].now_max + max_tag); t[x].now_add = t[x].now_add + now_tag;t[x].now_max = t[x].now_max + now_tag;}}inline void update (const int32_t x) {if (t[x].now_add != 0 || t[x].max_add != 0)putadd (lc, t[x].now_add, t[x].max_add), putadd (rc, t[x].now_add, t[x].max_add), t[x].now_add = t[x].max_add = 0;if (t[x].now_cov != - inf || t[x].max_cov != - inf)putcov (lc, t[x].now_cov, t[x].max_cov), putcov (rc, t[x].now_cov, t[x].max_cov), t[x].now_cov = t[x].max_cov = - inf;}inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (l == r) return t[x] = {a[l], a[l], 0, - inf, 0, - inf}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_add (const int32_t L, const int32_t R, const int64_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R) return putadd (x, v, v);update (x), opt_add (L, R, v, l, m, lc), opt_add (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_cov (const int32_t L, const int32_t R, const int64_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R) return putcov (x, v, v);update (x), opt_cov (L, R, v, l, m, lc), opt_cov (L, R, v, m + 1, r, rc), maintain (x);}inline int64_t que_max (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].now_max;update (x); return std::max (que_max (L, R, l, m, lc), que_max (L, R, m + 1, r, rc));}inline int64_t que_his (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].his_max;update (x); return std::max (que_his (L, R, l, m, lc), que_his (L, R, m + 1, r, rc));}}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n;for (int i = 1; i <= n; ++ i)std::cin >> a[i];segtree::build (), std::cin >> q;for (int i = 1, l, r, x; i <= q; ++ i) {char opt;std::cin >> opt;switch (opt) {case 'Q' : std::cin >> l >> r, std::cout << segtree::que_max (l, r) << '\n'; break ;case 'A' : std::cin >> l >> r, std::cout << segtree::que_his (l, r) << '\n'; break ;case 'P' : std::cin >> l >> r >> x, segtree::opt_add (l, r, x); break ;case 'C' : std::cin >> l >> r >> x, segtree::opt_cov (l, r, x); break ;}}return 0;
}
Luogu P6242 【模板】线段树 3(区间最值操作、区间历史最值)
给定序列,支持 区间加,区间对 \(x\) 取 \(\min\),求 区间和,区间 \(\max\),区间历史 \(\max\)
有区间最值操作 的 历史最值板子,但是 没有区间覆盖,好好好啊
同 CF1290E 的维护方式,将 最大值增量 和 其他值增量 分开维护
和上一道题一样需要维护 区间加(最大值,其他值)标记的 历史最大值
合并是简单的,可以直接写了,调不出来再参考代码
时间复杂度(有其他区间操作的 Segment Tree Beats)\(O (m \log ^ 2 n)\)
真的调不出来了?杂鱼~
# include <bits/stdc++.h>const int32_t maxn = 500005;const int64_t inf = 1145141919810;int32_t n, q;int32_t a[maxn];namespace segtree {struct node {int64_t now_sum, cnt_oth;int64_t fst_max, sec_max, his_max, cnt_max;int64_t nmx_add, hmx_add;int64_t noh_add, hoh_add;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].nmx_add = t[x].hmx_add = t[x].noh_add = t[x].hoh_add = 0;t[x].now_sum = t[lc].now_sum + t[rc].now_sum;t[x].fst_max = std::max (t[lc].fst_max, t[rc].fst_max);t[x].sec_max = std::max (t[lc].sec_max, t[rc].sec_max);t[x].his_max = std::max (t[lc].his_max, t[rc].his_max);t[x].cnt_max = t[x].cnt_oth = 0;t[x].cnt_max = t[x].cnt_max + (t[x].fst_max == t[lc].fst_max) * t[lc].cnt_max;t[x].cnt_max = t[x].cnt_max + (t[x].fst_max == t[rc].fst_max) * t[rc].cnt_max;t[x].cnt_oth = t[x].cnt_oth + t[lc].cnt_oth + (t[x].fst_max != t[lc].fst_max) * t[lc].cnt_max;t[x].cnt_oth = t[x].cnt_oth + t[rc].cnt_oth + (t[x].fst_max != t[rc].fst_max) * t[rc].cnt_max;if (t[lc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[lc].fst_max);if (t[rc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[rc].fst_max);}inline void putadd (const int32_t x, const int64_t now_othtag, const int64_t now_maxtag, const int64_t his_othtag, const int64_t his_maxtag) {t[x].now_sum = t[x].now_sum + now_othtag * t[x].cnt_oth + now_maxtag * t[x].cnt_max;t[x].his_max = std::max (t[x].his_max, t[x].fst_max + his_maxtag);if (t[x].fst_max != - inf) t[x].fst_max = t[x].fst_max + now_maxtag;if (t[x].sec_max != - inf) t[x].sec_max = t[x].sec_max + now_othtag;t[x].hmx_add = std::max (t[x].hmx_add, t[x].nmx_add + his_maxtag);t[x].hoh_add = std::max (t[x].hoh_add, t[x].noh_add + his_othtag);t[x].nmx_add = t[x].nmx_add + now_maxtag;t[x].noh_add = t[x].noh_add + now_othtag;}inline void update (const int32_t x) {if (t[x].nmx_add || t[x].noh_add || t[x].hmx_add || t[x].hoh_add) {int64_t fmax = std::max (t[lc].fst_max, t[rc].fst_max);if (t[lc].fst_max == fmax)putadd (lc, t[x].noh_add, t[x].nmx_add, t[x].hoh_add, t[x].hmx_add);elseputadd (lc, t[x].noh_add, t[x].noh_add, t[x].hoh_add, t[x].hoh_add);if (t[rc].fst_max == fmax)putadd (rc, t[x].noh_add, t[x].nmx_add, t[x].hoh_add, t[x].hmx_add);elseputadd (rc, t[x].noh_add, t[x].noh_add, t[x].hoh_add, t[x].hoh_add);t[x].noh_add = t[x].nmx_add = t[x].hoh_add = t[x].hmx_add = 0;}}inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (l == r) return t[x] = {a[l], 0, a[l], - inf, a[l], 1, 0, 0, 0, 0}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_add (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R) return putadd (x, v, v, v, v);update (x), opt_add (L, R, v, l, m, lc), opt_add (L, R, v, m + 1, r, rc), maintain (x);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R || t[x].fst_max <= v) return ;if (L <= l && r <= R && t[x].sec_max <= v) return putadd (x, 0, v - t[x].fst_max, 0, v - t[x].fst_max);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x);}inline int64_t que_sum (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return 0;if (L <= l && r <= R) return t[x].now_sum;update (x); return que_sum (L, R, l, m, lc) + que_sum (L, R, m + 1, r, rc);}inline int64_t que_nmx (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].fst_max;update (x); return std::max (que_nmx (L, R, l, m, lc), que_nmx (L, R, m + 1, r, rc)); }inline int64_t que_hmx (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return - inf;if (L <= l && r <= R) return t[x].his_max;update (x); return std::max (que_hmx (L, R, l, m, lc), que_hmx (L, R, m + 1, r, rc));}
}int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q;for (int i = 1; i <= n; ++ i)std::cin >> a[i];segtree::build ();for (int i = 1, opt, l, r, x; i <= q; ++ i) {std::cin >> opt;switch (opt) {case 1 : std::cin >> l >> r >> x, segtree::opt_add (l, r, x); break ;case 2 : std::cin >> l >> r >> x, segtree::opt_min (l, r, x); break ;case 3 : std::cin >> l >> r, std::cout << segtree::que_sum (l, r) << '\n'; break ;case 4 : std::cin >> l >> r, std::cout << segtree::que_nmx (l, r) << '\n'; break ;case 5 : std::cin >> l >> r, std::cout << segtree::que_hmx (l, r) << '\n'; break ;}}return 0;
}
附 `datamaker.cpp`,其实还是前面的 datamaker 改改就行
# include <bits/stdc++.h>std::mt19937 rd (std::random_device {} ());inline int64_t rnd (const uint32_t l = 1, const uint32_t r = - 1) {return rd () % (r - l + 1) + l;
}int32_t n;int32_t q;const int32_t N = 800;
const int32_t V = 1000;inline void solve (const int32_t Case) {n = rnd (N >> 1, N), q = rnd (N >> 1, N);std::cout << n << ' ' << q << '\n';for (int i = 1; i <= n; ++ i)std::cout << rnd (1, V) << ' ';std::cout << '\n';for (int i = 1, opt, l, r, x; i <= q; ++ i) {opt = rnd (1, 5), l = rnd (1, n), r = rnd (1, n), x = rnd (1, V);if (l > r) std::swap (l, r);if (opt == 1)std::cout << opt << ' ' << l << ' ' << r << ' ' << x << '\n';if (opt == 2)std::cout << opt << ' ' << l << ' ' << r << ' ' << x << '\n';if (opt == 3)std::cout << opt << ' ' << l << ' ' << r << '\n';if (opt == 4)std::cout << opt << ' ' << l << ' ' << r << '\n';if (opt == 5)std::cout << opt << ' ' << l << ' ' << r << '\n';}}int32_t T;int main () {// std::cin >> T;T = rnd (1, 1);// std::cout << T << '\n';for (int i = 1; i <= T; ++ i) solve (i);return 0;
}
经验 UOJ #169【UR #11】元旦老人与数列
Luogu P3246 [HNOI2016] 序列
给定序列,多次求 区间子区间最小值的和
方法非常的多,有 \(O (n \sqrt n)\) 莫队,有 \(O (n \log n)\) 甚至在线(也可以 \(O (n ^ 2)\) 暴力,大概
但是这里考虑一种 思路上十分自然 的 Segment Tree Beats 做法,写出来就是胜利
我们设 \(w (l, r) = \min (a _ l, a _ {l + 1}, ..., a _ r)\),则一个询问 \((l, r)\) 答案就是 \(\sum _ {i = l} ^ r \sum _ {j = i} ^ r w (i, j)\)
这就是 \(O (n ^ 2)\) 的暴力,考虑优化,我们令 \(s _ i = \sum _ {j = i} ^ r w (i, j)\),也就是左端点为 \(i\) 的 \(w\) 和
那么答案进一步变成 \(\sum _ {i = l} ^ r s _ i\),这时候如果我们能快速维护 \(s _ i\) 的区间和,那么题目就做完了
可以想到 线段树,由于 \(s _ i\) 钦定了 左端点,我们使用 扫描线 扫 右端点
直接维护 \(s _ i\) 是困难的
但是容易发现,扫 \(r \to r + 1\),等价于给 \(s _ 1 \sim s _ {r + 1}\) 加上 \(w _ {1, r + 1}, w _ {2, r + 1}, ..., w _ {r + 1, r + 1}\)
也就是说,\(s _ i\) 实际上就是 \(w _ i\) 的 历史和,而维护 \(w _ i\) 是简单的,就是一个 前缀对 \(x\) 取 \(\min\)
于是使用 Segment Tree Beats ,时间复杂度 \(O (m \log n)\)(不带其他区间操作)
好吗?好的
# include <bits/stdc++.h>const int32_t maxn = 100005;const int64_t inf = 0x3f3f3f3f;int32_t n, q;int32_t a[maxn];namespace segtree {struct node {int64_t now_sum, his_sum;int64_t fst_max, sec_max, cnt_max; int64_t max_add, add_cnt, his_add;} t[maxn << 2];# define lc (x << 1)# define rc (x << 1 | 1)# define m ((l + r) >> 1)inline void maintain (const int32_t x) {t[x].now_sum = t[lc].now_sum + t[rc].now_sum;t[x].his_sum = t[lc].his_sum + t[rc].his_sum;t[x].fst_max = std::max (t[lc].fst_max, t[rc].fst_max);t[x].sec_max = std::max (t[lc].sec_max, t[rc].sec_max);t[x].cnt_max = t[x].max_add = t[x].add_cnt = t[x].his_add = 0;t[x].cnt_max = t[x].cnt_max + (t[x].fst_max == t[lc].fst_max) * t[lc].cnt_max;t[x].cnt_max = t[x].cnt_max + (t[x].fst_max == t[rc].fst_max) * t[rc].cnt_max;if (t[lc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[lc].fst_max);if (t[rc].fst_max < t[x].fst_max)t[x].sec_max = std::max (t[x].sec_max, t[rc].fst_max);}inline void puttag (const int32_t x, const int64_t add_tag, const int64_t cnt_tag, const int64_t his_tag) {t[x].his_sum = t[x].his_sum + t[x].now_sum * cnt_tag + t[x].cnt_max * his_tag;t[x].now_sum = t[x].now_sum + t[x].cnt_max * add_tag;t[x].add_cnt = t[x].add_cnt + cnt_tag;t[x].his_add = t[x].his_add + t[x].max_add * cnt_tag + his_tag;t[x].max_add = t[x].max_add + add_tag;t[x].fst_max = t[x].fst_max + add_tag;}inline void update (const int32_t x) {if (t[x].add_cnt) {int64_t fmax = std::max (t[lc].fst_max, t[rc].fst_max);if (t[lc].fst_max == fmax)puttag (lc, t[x].max_add, t[x].add_cnt, t[x].his_add);elseputtag (lc, 0, t[x].add_cnt, 0);if (t[rc].fst_max == fmax)puttag (rc, t[x].max_add, t[x].add_cnt, t[x].his_add);elseputtag (rc, 0, t[x].add_cnt, 0);t[x].max_add = t[x].add_cnt = t[x].his_add = 0;} }inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (l == r) return t[x] = {a[l], 0, a[l], - inf, 1, 0, 0, 0}, void ();build (l, m, lc), build (m + 1, r, rc), maintain (x);}inline void opt_min (const int32_t L, const int32_t R, const int32_t v, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return ;if (L <= l && r <= R && t[x].fst_max <= v) return puttag (x, 0, 1, 0);if (L <= l && r <= R && t[x].sec_max <= v) return puttag (x, v - t[x].fst_max, 1, v - t[x].fst_max);update (x), opt_min (L, R, v, l, m, lc), opt_min (L, R, v, m + 1, r, rc), maintain (x);}inline int64_t que_sum (const int32_t L, const int32_t R, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {if (L > r || l > R) return 0;if (L <= l && r <= R) return t[x].his_sum;update (x); return que_sum (L, R, l, m, lc) + que_sum (L, R, m + 1, r, rc);}}struct ques {int32_t l, id;
};std::vector <ques> p[maxn];int64_t ans[maxn];int main () {std::ios::sync_with_stdio (0);std::cin.tie (0), std::cout.tie (0);std::cin >> n >> q;for (int i = 1; i <= n; ++ i)std::cin >> a[i];for (int i = 1, l, r; i <= q; ++ i) std::cin >> l >> r, p[r].push_back ({l, i});segtree::build ();for (int i = 1; i <= n; ++ i) {segtree::opt_min (1, i, a[i]);for (auto x : p[i])ans[x.id] = segtree::que_sum (x.l, i);}for (int i = 1; i <= q; ++ i)std::cout << ans[i] << '\n';return 0;
}
理论上,有了上面的东西,我们现在可以在 \(O (m \log ^ 2 n)\) 的时间内支持
区间加,区间覆盖,区间取反,区间乘,区间对 \(x\) 取 \(\min / \max\)
同时维护
区间(历史)和,区间(历史)(最 / 次)(大 / 小)值(个数)
嗯...