什么东西?DS!
Data Structure 2
不一定经典的 trick,但都比较简单,其中大部分是 Nityacke 做过的
线段树合并
Luogu P5384 雪松果树
给定一棵
个点的树,多次询问 ,求 节点 级祖先的 级儿子个数,
其实完全 不需要用线段树合并(
这个题做法很多,放在这里作为线段树合并 板子 以及
思路是很简单的,第一次 DFS 的过程中 记录路径
从而将询问
线段树维护 每个点子树内 每种深度的点 有多少个,朴素做法需要
于是考虑 动态开点,并在第二次 DFS 中做 线段树合并
如果询问 强制在线,可以使用 可持久化线段树合并,即每次合并的时候 创建一个新的节点
这样就可以在合并时 保留合并前两树的信息,时空复杂度不变,但是
倍常数
考虑到
正统的做法是 每次继承重儿子,然后暴力合并轻儿子,复杂度证明这里不赘述(可能是不会),形如
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]);
}
如果 随机继承一个儿子,那么可以被一个 形如毛毛虫 的形状卡掉。
注意到如果对于一个点,有
那么到链底时期望 同时存在 的线段树就有
如果需要使用 空间回收,那么请注意 回收站的大小,有时候仅开到
正确的代码
# 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;
}
这个题的标算还是挺有意思的,考虑 将询问挂到对应
后,直接 DFS 然后记录
,并 进行差分,即在 进入 子树时减,退出 子树时加 可以做到时空
在线的话等价于维护 区间某个颜色的数量,主席树可以时空
解决
一些会了这道题就应该会的题:
Luogu P7581 「RdOI R2」路径权值 (distance)
CF1051G Distinctification
给定
个二元组 ,有两种操作
,代价 ,当仅当存在 时可以使用 ,代价 ,当仅当存在 时可以使用 对于每个
,求使得前 对二元组中 两两不同的 最小代价
糖糖题,在
考虑对于一个 确定的序列 怎么做,手玩一下可以知道
进而容易发现,如果我们先把一个值域连续段里的数 均先变为最小值,其代价为一个 定值
即
这个时候考虑将其变为 两两不同的数,可以证明,最优情况下,
于是对
其中
注意排序时 除去 连续段中 最小
所对应的最大
最终将 多个值域连续段 的代价 加起来 就可以了
回到这个题,怎么维护 前
也就是说,我们需要支持在 插入任意
-
的 值域连续段可以用 并查集 方便的维护 右端点,用一个数组记录 当前右端点对应的左端点 就行
-
连续段中全变成最小值的代价
考虑 加入一个
,可能带来的影响无非是 合并两个段改变右端点时加入对应
即可,改变左端点时将对应段 整体左移( 改变)即可需要记录连续段对应的
的值,转移是 的 -
从大到小 排序的
权值线段树 容易维护,这里就出现问题了,我们需要对每个 值域连续段 用一棵线段树
而加入一个二元组可能造成 连续段的合并,所以对应的,我们也要 线段树合并 来维护
-
同样在 权值线段树 下维护即可,记录
三个信息,左右儿子合并 时即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
一棵树,点有 点权,选择一些点,使得所有被选择点对
满足若 在 子树内,则必须有 求 选出的点最大权值和
注意题意不要理解错,那么容易得到
设
枚举
上述操作本质就是将 儿子的 DP 值对位求和,然后将
可以想到 线段树来维护 DP 值,而 儿子求和 这个操作引出了 线段树合并 的做法
这个题的 合并部分 是简单的
难点在于 线段树合并通常只支持 有交换律的运算,换句话说,我们要做 标记永久化
一种思路 是和 Segment Tree Beats 一样考虑 均摊,因为每次操作就是一个 推平
而所有操作至多创造出
时间复杂度
还有一种 十分有趣的做法,也是笔者所采用的,我们分析
于是 后缀取
但是 区间赋值 标记 仍然不方便永久化,怎么办?
我们直接将这个区间完整包含的节点 全部删掉,然后给上一级区间 打上加法标记
也就等价于 全部推平成
自然就 不用考虑是否有交换律 的问题,于是相当于只剩下 加法标记,这是容易永久化的
为了找到所需要推平的区间,我们仍需区间
一种实现
# 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 最小割
给定一个图,满足所有 不在给定生成树上的边
,均满足 定义一个割合法,当且仅当其 恰好包含两条 生成树上的边
且割完后
与 不连通, 与 不连通 对于每条树边求出 包含这条边的最小割大小
你要先推一些东西,分两种情况
- 如果删去的两条 树边 没有祖先关系
那么显然能保留的 非树边 有且仅有 连接两条树边子树 的这些,其余的 都需要删掉
- 否则则可以证明 删去的两条边相邻 时不劣,对应答案是容易得到的
枚举(DFS)一条边,同时 DFS 得到另一条边在每个位置时 对应的答案,容易做到
此时用线段树维护 固定一条边,另一条边在不同位置时的答案,显然需要 全局最小值
初始所有权值均为
但是这样的话我们就有
直接使用 动态开点线段树 + 树上启发式合并 可以空间
但是我们有更好的做法,考虑直接 线段树合并,由于一共链减
于是空间理论上是
使用
于是 一个重链只用一棵线段树,而 dfs 的过程最劣情况就是到 叶子,此时有用信息是一条 根到叶的链
这其中至多切换
注意做好 节点回收
实测 用
写法 最大同时使用节点数 只有 个 但是用
写法 最大同时使用节点数 就有 个,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;
else
return 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
吉老师线段树,很好用啊(下文中的 区间最值操作 指 区间对
一些情况下也可以用 分块 在 不依赖均摊 的 情况下达到一样的效果,但是 复杂度更劣
其实更重要的是这种 均摊的思想,很多诡异的 DS 里面都可能涉及到
我先在这里丢个好东西,虽然里面的 有其他区间修改的 SegBeats 复杂度似乎有问题...
区间最值问题
HDU 5306 Gorgeous Sequence
区间对
取 ,求 区间 ,区间和,
没有其他区间修改 的 Segment Tree Beats 板子
若 区间对
注意到 区间对一个数取
在没有其他区间修改的情况下,每次操作 至多增加
换句话说,整个操作过程中,段数总和 是
只需要找到一种方法,可以在 较小时间 内 推平一个值相同段(减少一段),那么均摊后复杂度就是 可接受的
Segment Tree Beats 就是这样的一种方法,其可以在
于是当 没有其他区间修改(总段数
具体实现上,我们记录区间的 最大值
只找到
考虑我们需要 额外维护哪些信息?
由于需要 区间和,故维护区间和
每次赋值即将区间 所有 最大值 从
最后考虑 区间推平 这个信息需要一个 下传的懒标记,于是其 信息和标记完整了,合并是简单的
如果区间对
一种区间对 取 的实现(本题)
# 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';
else
std::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)
区间对
取 ,区间加,求 区间 ,区间和,
有其他区间修改 的 Segment Tree Beats 板子,小清新啊
提供一种比较感性的复杂度分析,考虑一次 其他区间操作 对一个 区间最值操作 带来的影响
通过上一题发现,区间最值操作 时间与形如
而一次 其他区间操作,影响
只有路径上的 个节点有可能改变,故其与 的关系也只有 个节点有可能改变
那么总共新建的这样节点个数就是
虽然 不带其他区间修改的 SegtBeats 常数较大
但由于 一次其他区间操作 很难改变满
个节点的 大小关系 故其实 带其他区间修改的 SegtBeats 常数 反而不大
可以从这两道题的 实际运行结果看出端倪
具体实现上,我们只需要比上一道题多考虑三种东西:
-
区间最值操作 之间 标记的 复合
对于影响到的区间,其类似 区间赋值标记,故下传时直接 对儿子标记取
即可 -
区间加 标记与 区间最值操作标记 之间的复合
同样的将 区间最值操作 当作 区间赋值 来考虑,于是下传时 将 儿子区间最值标记 加 父亲区间加标记值
-
标记下传顺序
先加再覆盖,同理,先 区间加标记 再 区间最值操作标记
这里的 标记下传顺序,先加再覆盖和先覆盖再加可能都可以?有没有人是反过来的?
然后直接开写就行了,需要调试可以使用上一题的 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;
}
吉老师的原论文中还有 几个 只涉及 区间最值操作 的原创例题,由于没有提交途径,就不放在这里了
都是一些小
的复合,在 trsins 老师的某篇博客中也有对那 几道 题的讲解
CF1290E Cartesian Tree
给定
的排列 对于所有
,求 只考虑 的数的数列 的 笛卡尔树 上 每个点的 子树大小和
比较简单的应用,仍然先考虑 静态问题,也就是假设给定序列,怎么求 子树大小和
可以发现,一个数对应的点的 子树 就是在数列上 前后第一个比它大的数 之间的数 对应的点
设
容易发现
这时候考虑 插入一个数 带来的影响,可以发现每次插入的数均为 最大值,设插在位置
显然,位置在
对于位置在
于是得到了动态维护
使用 Segment Tree Beats 即可方便维护,对于这个题,由于需要每次 加点
所以直接维护 极值 / 其余值 个数,极值 / 其余值 增量 会比模板题 区间覆盖 的写法好一些
注意,需要在 下传标记 时讨论 最大值位置 并 分别处理
复杂度,显然这是 有其他区间操作的情况,故为
一种实现
# 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);
else
puttag (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 第三题
给定
个集合 ,其中第 个集合包含了 内的所有整数 进行
次询问,每次给定 ,你需要求出所有被 包含的集合的并集大小
是个 Segment Tree Beats 套 树状数组 的题
我和 🚗 场上看错了两种不同的题意,使得
考虑 值域扫描线,当然先 离散化,给 每个点(值域上)放一个权值,即 到后面点的距离
从左向右扫右端点,我们考虑记录每个值域点在 左端点至少多少 的时候可以被覆盖
于是加入一个 条件区间
一个询问
用 Segtree Beats 套一个 树状数组 就可以 较为简单的解决
显然 没有其他的区间操作,于是前面 Segment Tree Beats 是
然后树状数组有一个
注意每次只在 大区间被修改时修改树状数组,下传标记到小区间时 不动树状数组
否则虽然正确性大致没有问题,但是时间上会多一个
效率不错的实现
# 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;
else
c.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】前进四
给定序列,单点修改,单点查询 后缀最小值的不同值个数
结合 换维扫描线,可以自行思考
注意到我们需要 后缀信息,所以可以考虑操作 序列维
更进一步的,考虑 从后向前 扫描序列,先考虑 直接维护每个点答案是否能做
容易发现一个点会在 不同时间查询多次,直接维护点 会倒闭
这启发我们维护 时间轴,这时候考察 修改的贡献,其使得 一个数划分成了多个时间段
其中 每个时间段值是一样的,且总段数
可以发现,当扫到
即在
时刻,从 开始的 后缀最小值 大于 ,故此时若 ,则有贡献
换言之,我们将
于是我们需要支持 区间对
只需要维护 最大值,次大值,最大值增量,影响位置增量 即可,时间复杂度
这个都写不出来?♡ 真是弱哎 ♡ ~
# 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;
}
BZOJ 5312 冒险(Baekjoon 17473 수열과 쿼리 25)
笔者 无法在 任何 BZOJ 镜像站 找到原题,故用原题机得到 题意完全一致 的题目以提交
给定序列,支持 区间按位与
,区间按位或 ,查询 区间
可能第一时间不能想到 Segment Tree Beats,但是感性理解可以发现
区间按位与 / 按位或 一定次数次之后 会使得这个区间 趋于相同
如果每次操作 至少让一个区间的一位 变成相同的,那么这个区间至多被操作
可以证明这样总操作不超过
我们维护区间的 按位与和 和 按位或和,它们 异或 的结果 就是 区间内有差异的位
如果操作前后,这些位有改变,那么说明 至少一位变成相同的
显然可以证明这样的位 数量单调不降,也就是 不可能相同的位数变少
于是当遇到这样的情况,我们 直接递归下去
否则,由于操作前后 有差异的位 不变,我们可以 整体打标记 维护区间的值
每次这样 递归下去 一次,区间内 有差异的位 至少减少
代码!
# include <bits/stdc++.h>
const int32_t maxn = 200005;
const int32_t inf = 0b01111111111111111111111111111111;
int32_t n, q;
int32_t a[maxn];
namespace segtree {
struct node {
int32_t or_sum, ad_sum, max;
int32_t or_tag, ad_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].or_tag = 0, t[x].ad_tag = + inf;
t[x].or_sum = t[lc].or_sum | t[rc].or_sum;
t[x].ad_sum = t[lc].ad_sum & t[rc].ad_sum;
t[x].max = std::max (t[lc].max, t[rc].max);
}
inline void puttag (const int32_t x, const int32_t or_tag, const int32_t ad_tag) {
t[x].ad_tag = (t[x].ad_tag & ad_tag);
t[x].or_tag = (t[x].or_tag & ad_tag) | or_tag;
t[x].max = (t[x].max & ad_tag) | or_tag;
t[x].or_sum = (t[x].or_sum & ad_tag) | or_tag;
t[x].ad_sum = (t[x].ad_sum & ad_tag) | or_tag;
}
inline void update (const int32_t x) {
if (t[x].ad_tag == + inf && t[x].or_tag == 0) return ;
puttag (lc, t[x].or_tag, t[x].ad_tag);
puttag (rc, t[x].or_tag, t[x].ad_tag);
t[x].or_tag = 0, t[x].ad_tag = + 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], a[l], 0, + inf}, void ();
build (l, m, lc), build (m + 1, r, rc), maintain (x);
}
inline bool same_after_opt (const int32_t or_sum, const int32_t ad_sum, const int32_t or_val, const int32_t ad_val) {
return ((or_sum ^ ad_sum) & (~ ad_val | or_val)) == 0;
}
inline void opt_mod (const int32_t L, const int32_t R, const int32_t or_val, const int32_t ad_val, 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 && same_after_opt (t[x].or_sum, t[x].ad_sum, or_val, ad_val)) return puttag (x, or_val, ad_val);
update (x), opt_mod (L, R, or_val, ad_val, l, m, lc), opt_mod (L, R, or_val, ad_val, m + 1, r, rc), maintain (x);
}
inline int32_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 0;
if (L <= l && r <= R) return t[x].max;
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::build (), std::cin >> q;
for (int i = 1, opt, l, r, x; i <= q; ++ i) {
std::cin >> opt >> l >> r;
switch (opt) {
case 1 :
std::cin >> x, segtree::opt_mod (l, r, 0, x); break ;
case 2 :
std::cin >> x, segtree::opt_mod (l, r, x, + inf); break ;
case 3 :
std::cout << segtree::que_max (l, r) << '\n'; break ;
}
}
return 0;
}
历史最值问题
可能绝大多数人都学过,但是不熟悉的话可以借此复习一下?
真的的会有人想复习这种东西?
我是史学家,这就是史
Luogu P4314 CPU 监控
给定序列,支持区间加,区间覆盖,维护 区间
,区间历史 再过两年这东西不会 降蓝 吧
没有区间最值操作 的 历史最值板子,仍然先考虑需要 哪些信息和标记
信息可以只需要 区间最大值 和 区间历史最大值 即可
标记至少也有 区间加 和 区间覆盖标记,但这 显然不够
容易发现,如果整体先加一个正数,再加一个负数,此时 只维护区间加 就会将操作 合并
但是显然这样合并对于历史最大值 是不优的,其会在 加正数的时候 取到最大
这启发我们维护 区间加标记的历史最大值,同理会有 区间覆盖标记的历史最大值
剩下唯一的问题是 标记的复合,感觉可以直接做?但有一些 细节
注意到 两个历史标记 实际上是指的 上次下传之后的历史最大值,故我们是需要下传清空的
下传顺序 先加再覆盖,若下传加法标记时此时 儿子覆盖标记有值,则直接 加在标记上
也就是说,我们将覆盖操作之后的所有操作 都视作覆盖操作
时间复杂度(有其他区间操作的 Segment Tree Beats)
直接看代码会好理解很多
# 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(区间最值操作、区间历史最值)
给定序列,支持 区间加,区间对
取 ,求 区间和,区间 ,区间历史
有区间最值操作 的 历史最值板子,但是 没有区间覆盖,好好好啊
同 CF1290E 的维护方式,将 最大值增量 和 其他值增量 分开维护
和上一道题一样需要维护 区间加(最大值,其他值)标记的 历史最大值
合并是简单的,可以直接写了,调不出来再参考代码
时间复杂度(有其他区间操作的 Segment Tree Beats)
真的调不出来了?杂鱼~
# 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);
else
putadd (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);
else
putadd (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;
}
Luogu P3246 [HNOI2016] 序列
给定序列,多次求 区间子区间最小值的和
方法非常的多,有
但是这里考虑一种 思路上十分自然 的 Segment Tree Beats 做法,写出来就是胜利
我们设
这就是
那么答案进一步变成
可以想到 线段树,由于
直接维护
但是容易发现,扫
也就是说,
于是使用 Segment Tree Beats ,时间复杂度
好吗?好的
# 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);
else
puttag (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);
else
puttag (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;
}
理论上,有了上面的东西,我们现在可以在
的时间内支持 区间加,区间覆盖,区间取反,区间乘,区间对
取 同时维护
区间(历史)和,区间(历史)(最 / 次)(大 / 小)值(个数)
嗯...
李超线段树
板子跳过了,这个东西多数时候用于 优化 DP 或者 凸包一类的东西
Luogu P4655 [CEOI2017] Building Bridges
zhicheng:这个不是典?这个和板题一样的!
有
根柱子,分别高 ,同时有权 ,在两个柱子 上搭桥代价为 没有桥的柱子 都需要用
代价拆除,问 的 最小总代价
考虑 DP,设
容易得出转移式
我们先把里面的 常数 拿出来,有
此时发现与
于是设
这个时候就可以上 李超线段树 了,我们每计算出一个
然后查询
**zhicheng**:笨蛋 ~ 这都不会?
# include <bits/stdc++.h>
const int32_t maxn = 100005;
const int32_t maxv = 1000009;
const int64_t inf = 1e18;
int32_t n;
int64_t f[maxn], h[maxn], w[maxn];
struct segment {
int64_t k, b;
inline int64_t val (const int64_t x) {
return x * k + b;
}
} s[maxn];
namespace lc_segtree {
int32_t t[maxv << 2];
# define lc (x << 1)
# define rc (x << 1 | 1)
# define m ((l + r) >> 1)
inline void cmp_swap (const int32_t x, const int32_t y, const int32_t p) {
if (s[t[x]].val (p) > s[y].val (p)) t[x] = y;
}
inline void opt_ins (int32_t p, const int32_t l = 0, const int32_t r = maxv, const int32_t x = 1) {
if (l == r) return cmp_swap (x, p, l);
if (s[t[x]].val (m) > s[p].val (m)) std::swap (p, t[x]);
if (s[t[x]].val (l) > s[p].val (l)) opt_ins (p, l, m, lc);
if (s[t[x]].val (r) > s[p].val (r)) opt_ins (p, m + 1, r, rc);
}
inline int64_t que_val (const int32_t p, const int32_t l = 0, const int32_t r = maxv, const int32_t x = 1) {
if (l == r) return s[t[x]].val (p);
return std::min (s[t[x]].val (p), p <= m ? que_val (p, l, m, lc) : que_val (p, 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 >> h[i];
for (int i = 1; i <= n; ++ i)
std::cin >> w[i], w[i] = w[i] + w[i - 1];
s[1] = {- 2 * h[1], h[1] * h[1] - w[1]};
s[0] = {0, + inf}, lc_segtree::opt_ins (1);
for (int i = 2; i <= n; ++ i) {
f[i] = h[i] * h[i] + w[i - 1] + lc_segtree::que_val (h[i]);
s[i] = {- 2 * h[i], f[i] + h[i] * h[i] - w[i]};
lc_segtree::opt_ins (i);
}
std::cout << f[n] << '\n';
return 0;
}
CF932F Escape Through Leaf
给定树,每个点有权
,从 的花费为 ,路径费用为边费用和 分别求出 每个点到一个叶子的最小花费
李超线段树合并,朴素的
也就是你要求出 整个子树内的最小值,考虑设
可以想到 李超线段树,再 子树内 这个限制,容易想到 线段树合并,套起来就行
时间复杂度 有一个有趣的证明,考虑一条直线在 线段树上的位置
容易发现一次更新至少使得 一条直线深度
于是合并这个部分 均摊下来时间复杂度为
瑟
# include <bits/stdc++.h>
const int32_t maxn = 100005;
const int32_t maxv = 100005;
const int64_t inf = 1e18;
const int32_t logn = 20;
int64_t a[maxn], b[maxn], w[maxn], s[maxn];
std::vector <int32_t> g[maxn];
struct segment {
int64_t k, b;
inline int64_t val (const int64_t x) {
return x * k + b;
}
} seg[maxn];
namespace segtree {
struct node {
int32_t ls, rs, v;
} t[maxn * logn];
# define lc (t[x].ls)
# define rc (t[x].rs)
# define m ((l + r) >> 1)
int32_t rt[maxn], cnt = 0;
int32_t tr[maxn], top = 0;
inline int32_t newnode () {
return top ? tr[top --] : ++ cnt;
}
inline void push (const int32_t x) {
t[x] = {0, 0, 0}, tr[++ top] = x;
}
inline bool cmp (const int32_t x, const int32_t y, const int32_t p) {
return seg[x].val (p) < seg[y].val (p);
}
inline void opt_ins (int32_t p, int32_t & x, const int32_t l = - maxv, const int32_t r = maxv) {
if (! x) return x = newnode (), t[x].v = p, void ();
if (l == r)
return (cmp (p, t[x].v, l) ? t[x].v = p : 0), void ();
if (cmp (p, t[x].v, m)) std::swap (p, t[x].v);
if (cmp (p, t[x].v, l)) opt_ins (p, lc, l, m);
if (cmp (p, t[x].v, r)) opt_ins (p, rc, m + 1, r);
}
inline int64_t que_val (const int32_t p, const int32_t x, const int32_t l = - maxv, const int32_t r = maxv) {
if (! x) return + inf;
if (l == r) return seg[t[x].v].val (p);
return std::min (seg[t[x].v].val (p), p <= m ? que_val (p, lc, l, m) : que_val (p, rc, m + 1, r));
}
inline int32_t merge (int32_t x, const int32_t y, const int32_t l = - maxv, const int32_t r = maxv) {
if (! x || ! y) return x + y;
opt_ins (t[y].v, x, l, r);
lc = merge (t[x].ls, t[y].ls, l, m);
rc = merge (t[x].rs, t[y].rs, m + 1, r);
return x;
}
}
int32_t n;
inline void dfs (const int32_t x, const int32_t f) {
s[x] = 1;
for (auto i : g[x])
if (i != f)
dfs (i, x), segtree::rt[x] = segtree::merge (segtree::rt[x], segtree::rt[i]), s[x] += s[i];
w[x] = segtree::que_val (a[x], segtree::rt[x]) * (s[x] != 1);
seg[x] = {+ b[x], + w[x]};
segtree::opt_ins (x, segtree::rt[x]);
}
int main () {
std::ios::sync_with_stdio (0);
std::cin.tie (0), std::cout.tie (0);
std::cin >> n, seg[0].b = + inf;
for (int i = 1; i <= n; ++ i)
std::cin >> a[i];
for (int i = 1; i <= n; ++ i)
std::cin >> b[i];
for (int i = 2, u, v; i <= n; ++ i) {
std::cin >> u >> v;
g[u].push_back (v), g[v].push_back (u);
}
dfs (1, 0);
for (int i = 1; i <= n; ++ i)
std::cout << w[i] << ' ';
return 0;
}
Luogu P5508 寻宝
被 Nityacke 睿频是 食,虽然确实不好吃就是了
给定
个点,有点权 ,边均 有向,有 条 牛逼边 表示
中 任意点 可以花费 代价到 中 任意点 你可以建 若干 垃圾边
,各花费 的代价,问 最小代价
容易有
我们先考虑 牛逼边,容易想到 线段树优化建图,那就直接上线段树
然后考虑 最小代价,可以想到 最短路,那就直接上 Dijkstra,显然只剩 垃圾边 了
你发现钦定
使用 李超线段树 来维护这种东西,注意到需要 插入线段,时间是
于是在 线段树 上做 Dijkstra 的时候,每次取出 堆顶 与 李超线段树 里的全局
取出 较小的点 开始跑,如果这个点在 李超树 上,需要将 这个点删掉(不能再次取到)
也就是设成
,同时线段
得到一个点的
最短路径方案只需记录 转移时小于
注意到你 李超树 上由于需要动态维护 最小值,同时 单点删除(查询)
所以要在 插入和删除 时进行对应的 下传上传 操作,和普通线段树相似
(比如 仅有一个线段覆盖整区间,但是你要 单点删除,于是需要下传这个线段)
设定势能为 一条线段在一个区间 的 深度,一次插入最多将一条线段拆分到
一次下传会用
常数比较大,实现可能较为复杂,应当结合代码理解
谁批准了?
# include <bits/stdc++.h>
const int32_t maxm = 50005;
const int32_t maxn = 550005;
const int64_t inf = 1e18;
struct edge {
int32_t x, w;
};
std::vector <edge> g[maxn];
int32_t n, q;
# define lc (x << 1)
# define rc (x << 1 | 1)
# define m ((l + r) >> 1)
int32_t cnt = 0;
inline void add_edge (const int32_t x, const int32_t y, const int32_t w = 0) {
g[x].push_back ({y, w});
}
namespace segtree1 {
inline int32_t w (const int32_t x, const int32_t d) {
return x + n + d * n * 4;
}
inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {
if (l == r) return add_edge (l, w (x, 0)), add_edge (w (x, 1), l);
build (l, m, lc), build (m + 1, r, rc);
add_edge (w (lc, 0), w (x, 0)), add_edge (w (rc, 0), w (x, 0));
add_edge (w (x, 1), w (lc, 1)), add_edge (w (x, 1), w (rc, 1));
}
inline void add (const int32_t L, const int32_t R, const int32_t v, const int32_t f, 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 && f == 0) return add_edge (w (x, 0), v);
if (L <= l && r <= R && f == 1) return add_edge (v, w (x, 1));
add (L, R, v, f, l, m, lc), add (L, R, v, f, m + 1, r, rc);
}
}
struct seg {
int64_t k, b;
inline int64_t val (const int32_t x) {
return k * x + b;
}
} s[maxm << 1];
int32_t tot = 0;
namespace segtree2 {
struct segment {
int32_t l, r, f;
int32_t id, del;
int64_t min;
} t[maxm << 2], result;
inline void maintain (const int32_t x) {
if (t[x].del) return ;
if (t[lc].del && t[rc].del)
return t[x].del = 1, t[x].min = + inf, void ();
t[x].l = t[lc].del ? t[rc].l : t[lc].l;
t[x].r = t[rc].del ? t[rc].r : t[lc].r;
t[x].min = std::min ({t[lc].min, t[rc].min, s[t[x].id].val (t[x].l), s[t[x].id].val (t[x].r)});
}
inline void build (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {
t[x] = {l, r, 0, 0, 0, + inf};
if (l == r) return ;
build (l, m, lc), build (m + 1, r, rc);
}
inline bool cmp (const int32_t x, const int32_t y, const int32_t p) {
return s[x].val (p) <= s[y].val (p);
}
inline void ins (int32_t v, int32_t f, const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {
if (t[x].del) return ;
if (cmp (v, t[x].id, m)) std::swap (v, t[x].id), std::swap (f, t[x].f);
if (l == r) return t[x].min = std::min (t[x].min, s[t[x].id].val (m)), void ();
if (cmp (v, t[x].id, l)) ins (v, f, l, m, lc);
if (cmp (v, t[x].id, r)) ins (v, f, m + 1, r, rc);
maintain (x);
}
inline void update (const int32_t x, const int32_t l, const int32_t r) {
if (t[x].del || ! t[x].id || l == r) return ;
ins (t[x].id, t[x].f, l, m, lc);
ins (t[x].id, t[x].f, m + 1, r, rc);
t[x].id = t[x].f = 0, maintain (x);
}
inline void add (const int32_t L, const int32_t R, const int32_t v, const int32_t f, 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 ins (v, f, l, r, x);
update (x, l, r), add (L, R, v, f, l, m, lc), add (L, R, v, f, m + 1, r, rc), maintain (x);
}
inline void getans (const int32_t l = 1, const int32_t r = n, const int32_t x = 1) {
if (l == r) return result = t[x], t[x].del = 1, t[x].min = + inf, void ();
update (x, l, r), t[lc].min <= t[rc].min ? getans (l, m, lc) : getans (m + 1, r, rc); maintain (x);
}
inline void add_line (const int32_t f, const int64_t k, const int64_t b, const int32_t l, const int32_t r) {
s[++ tot] = {k, b}, add (l, r, tot, f);
}
}
int32_t vis[maxn], frm[maxn], now;
int64_t dis[maxn], val[maxm];
struct node {
int64_t x, d;
inline bool operator < (const node & a) const {
return d > a.d;
}
};
inline void dijkstra () {
for (int i = 1; i <= cnt; ++ i) dis[i] = + inf;
std::priority_queue <node> pq; pq.push ({1, dis[1] = 0});
while (pq.size () || segtree2::t[1].min != inf) {
if (pq.empty () || segtree2::t[1].min < pq.top ().d) {
segtree2::getans (), now = segtree2::result.l;
if (vis[now]) continue ;
dis[now] = segtree2::result.min, frm[now] = segtree2::result.f;
} else now = pq.top ().x, pq.pop ();
if (vis[now]) continue ;
vis[now] = 1;
for (auto x : g[now])
if (dis[now] + x.w < dis[x.x])
dis[x.x] = dis[now] + x.w, frm[x.x] = now, pq.push ({x.x, dis[x.x]});
if (now <= n && val[now]) {
if (now != 1) segtree2::add_line (now, - val[now], dis[now] + val[now] * now, 1, now - 1);
if (now != n) segtree2::add_line (now, + val[now], dis[now] - val[now] * now, now + 1, n);
}
}
}
std::vector <int32_t> ans;
int main () {
std::ios::sync_with_stdio (0);
std::cin.tie (0), std::cout.tie (0);
std::cin >> n >> q, s[0].b = + inf;
for (int i = 1; i <= n; ++ i)
std::cin >> val[i];
cnt = 9 * n, segtree1::build (), segtree2::build ();
for (int i = 1, l_1, l_2, r_1, r_2, w; i <= q; ++ i) {
std::cin >> l_1 >> r_1 >> l_2 >> r_2 >> w;
segtree1::add (l_1, r_1, ++ cnt, 0);
segtree1::add (l_2, r_2, ++ cnt, 1);
add_edge (cnt - 1, cnt - 0, w);
}
dijkstra ();
if (dis[n] == + inf)
return std::cout << - 1 << '\n', 0;
for (int i = n; i != 1; i = frm[i])
if (i <= n) ans.push_back (i);
ans.push_back (1), std::reverse (ans.begin (), ans.end ());
std::cout << dis[n] << '\n' << ans.size () << '\n';
for (auto i : ans) std::cout << i << ' ';
return 0;
}
附 `datamaker`
# include <bits/stdc++.h>
const int32_t maxn = 100005;
const int32_t n = 50000;
const int32_t m = 50000;
const int32_t v = 1000000000;
const int32_t w = 1000000000;
std::mt19937 rd (std::random_device {} ());
inline int64_t rng (const int32_t l, const int32_t r) {
return rd () % (r - l + 1) + l;
}
int main () {
std::cout << n << ' ' << m << '\n';
for (int i = 1; i <= n; ++ i)
std::cout << rng (0, v) << ' ';
std::cout << '\n';
for (int i = 1; i <= m; ++ i) {
int32_t l_1 = rng (1, n), r_1 = rng (1, n);
int32_t l_2 = rng (1, n), r_2 = rng (1, n);
if (l_1 > r_1) std::swap (l_1, r_1);
if (l_2 > r_2) std::swap (l_2, r_2);
std::cout << l_1 << ' ' << r_1 << ' ' << l_2 << ' ' << r_2 << ' ' << rng (1, w) << '\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具