becoder 线段树分治题单

news/2025/3/26 15:16:56/文章来源:https://www.cnblogs.com/yzq-yzq/p/18789841

线段树分治

假如你需要维护一些信息,这些信息会在某一个时间段内出现,要求在离线的前提下回答某一个时刻的信息并,则可以考虑使用线段树分治。

典型的比如连通性,线性基等难以支持删除的信息。

简单概述 OJ 题单中 14 道题的做法,难度按个人认为的升序排序。

除此之外再提几道比较 educational 的线段树分治的题目,会打上 *。

「TJOI2018」数学计算

https://www.becoder.com.cn/problem/4413

因为 \(mod\) 不保证质数,所以不一定存在逆运算。

考虑线段树分治,然后只有乘法。

「BZOJ3237 Ahoi2013」连通图

https://www.becoder.com.cn/problem/17237

维护连通性板子,正常维护连通性是使用并查集的,但是这里需要撤销。

栈序撤销并查集

路径压缩过后并查集很难支持撤销,于是考虑通过启发式合并降低 find 的复杂度,把 fa 与 siz 的修改用栈存下来然后撤回。实现如下。

#define pi pair<int, int>
#define fi first
#define se second
const int N = 200020;
struct DSU {int fa[N], siz[N];stack<pi> stk;DSU() {rep(i, 0, N - 1)fa[i] = i, siz[i] = 1;}inline int find(int x) {while(fa[x] != x) x = fa[x];return x;}inline void merge(int x, int y) {x = find(x), y = find(y);if(x == y) return;if(siz[x] > siz[y]) swap(x, y);stk.push({x, y});fa[x] = y, siz[y] += siz[x];}inline void undo(int lst) {while(stk.size() > lst) {pi x = stk.top();stk.pop();fa[x.fi] = x.fi;siz[x.se] -= siz[x.fi];}}
} D;

注意复杂度是 \(n\log^2 n\) 的。

接着几个是维护并查集的例题

「CF1140F」Extending Set of Points

https://www.becoder.com.cn/problem/32792

首先通过线段树分治变成只加点。

然后考虑只有加入怎么求点的个数。

考虑构造一个无向图,有 \(n\) 个点 \(X_i\) 以及 \(n\) 个点 \(Y_i\),把集合加入一个点 \((a,b)\) 转化成给无向图中加一条边 \((x_a,Y_b)\) ,然后不难发现答案应该是无向图中每个连通块中 \(X\) 点与 \(Y\) 点个数的成绩,并查集维护即可。

「SHOI 2008」堵塞的交通

https://www.becoder.com.cn/problem/4931

维护连通性板子,不赘述。

「SHOI2014」神奇化合物

https://www.becoder.com.cn/problem/4056

也是板子,不赘述。

*P5787 二分图 /【模板】线段树分治

https://www.luogu.com.cn/problem/P5787

二分图判定同样也是可以通过并查集实现的,通过种类并查集将 \(i,j\) 颜色不等转化成连边 \((i+n,j),(i,j+n)\) ,然后判断是否对任意 \(i,i+n\) 均不在同一连通块中即可。

Painting Edges

https://www.becoder.com.cn/problem/23537

线段树分治维护二分图判定的板子题。

但是值得注意的是这个题中出现了加入加边不合法则无视该操作,处理的方法是假如一条边是不合法的,那么我们就先打上 tag,然后之后线段树分治的时候加边前先判断是否被打了 tag。


线性基同样也是可以支持栈序撤销的。

考虑用个栈存成功插入的元素,然后因为是栈序撤销,撤销时直接把插入位置清空即可。

当然有的时候因为线性基大小是很小的,直接暴力复制一个也是可以过的。


「BZOJ4184」 shallot

https://www.becoder.com.cn/problem/18184

线段树分治维护线性基板子。

贴个没有栈序撤销的代码。

#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; ++i)
#define drep(i, x, y) for (int i = x; i >= y; --i)
#define ll long long
#define pb push_back
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
using namespace std;
#define ui unsigned int
const int N = 500050;
struct D {ui f[32];D() { rep(i, 0, 30) f[i] = 0; }inline void ins(ui x) {drep(i, 30, 0) {if (x >> i & 1) {if (f[i]) {x ^= f[i];continue;}f[i] = x;return;}}}inline ui Q() {ui res = 0;drep(i, 30, 0) if ((res ^ f[i]) > res) res ^= f[i];return res;}
};
int n, a[N];
unordered_map<int, int> lst;
ui ans[N];
vector<ui> T[N << 2];
void ins(int ql, int qr, int x, int p = 1, int l = 1, int r = n) {if (qr < l || r < ql)return;if (ql <= l && r <= qr)return void(T[p].pb(x));int mid = (l + r) >> 1;ins(ql, qr, x, p << 1, l, mid);ins(ql, qr, x, p << 1 | 1, mid + 1, r);
}
void solve(int p, int l, int r, D x) {for (ui y : T[p]) x.ins(y);if (l ^ r) {int mid = (l + r) >> 1;solve(p << 1, l, mid, x);solve(p << 1 | 1, mid + 1, r, x);} else {ans[l] = x.Q();}
}
int main() {IOS;cin >> n;rep(i, 1, n) {cin >> a[i];if (a[i] < 0)ins(lst[-a[i]], i - 1, -a[i]), lst[-a[i]] = 0;elselst[a[i]] = i;}for (auto x : lst) {if (x.second != 0)ins(x.second, n, x.first);}D ep;solve(1, 1, n, ep);rep(i, 1, n) cout << ans[i] << '\n';return 0;
}

「BZOJ4644」经典傻逼题

题目名字怎么骂藏?

https://www.becoder.com.cn/problem/18644

但是确实是 sb 题,难点在读题。

定义 \(val_x\) 为与 \(x\) 相连的所有边的边权的异或和,然后不难发现答案应该是 \(val\) 的最大异或和,因为有修改,线段树分治即可。


假如你做过 [WC2011] 最大XOR和路径 那么就会有并查集与线性基同时使用的题目。

「HAOI2017」八纵八横

https://www.becoder.com.cn/problem/4164

根据 [WC2011] 最大XOR和路径 的做法,应当每次把环的边权和加入线性基中。

于是考虑并查集,维护每个点到根的边权异或和,然后再用栈序撤销线性基就行。

这个题输入输出比较恶心,注意一下。

「CF1442D」选数

https://www.becoder.com.cn/problem/31218

运用了线段树分治的思想,因为 \(t_i\ge n\) ,于是可以考虑这样的策略,枚举 \(i\) ,然后求出除了 \(i\) 之外的栈的背包 dp,然后枚举当前栈选几个。

然后直接线段树分治加背包就行,这个题性质比较好,不需要写插入,直接分治即可。

「雅礼集训 2018 Day10」贪玩蓝月

https://www.becoder.com.cn/problem/5503

经典题,不难发现线段树分治是可做的,但是过不去。

不难发现我们实际上要维护这样的一个问题,有一个序列,要在开头加入删除或者末尾加入删除,然后维护序列的和但是只满足结合律,要做到线性。

做法是双栈,具体双栈的介绍以及复杂度说明可以看这个文章 :https://www.becoder.com.cn/article/4282

「FJOI2015」火星商店问题

https://www.becoder.com.cn/problem/6483

个人认为这东西应该用线段树套 Trie 树是比较直接的。然后假如要优化空间可以离线一下空间少一只 \(\log\)

假如有线段树分治做法敲我,可能是我太菜了。

「BZOJ4311」向量

https://www.becoder.com.cn/problem/18311

经典的一个套路,求与 \((a,b)\) 点乘的最大值其实 \(ax+by=a(x+\frac b ay)\) ,然后不难发现后边是个直线 \(kx+b\) 的形式,于是可以用李超线段树解决。

因为有删除可以考虑给李超线段树栈序撤销或者可持久化。

李超线段树是可以可持久化的哦,空间也是 \(1\log\)

「CTSC2016」时空旅行

https://www.becoder.com.cn/problem/4933

很大一坨东西是定值,然后发现问题变成了 「BZOJ4311」向量 ,直接套上去就行了。

但是这样做有点卡空间。


OJ 上边的题就这些了,接着提下一些其它比较有价值的东西。

首先线段树是可以栈序撤销的,用个栈存修改的点的下标然后回撤回去。

假如只有单点修改空间是可以做到线性的,直接把修改的叶子节点存下来,然后撤回时先回撤叶子节点,然后将叶子到根的路径重新 pushup 更新一遍。

*P11619 [PumpkinOI Round 1] 种南瓜

https://www.luogu.com.cn/problem/P11619

不难发现题意应该是询问是否存在两个区间只相交但互不包含。

假如只有加入是好做的,我们每次加入 \([l,r]\) ,要判断是否有 \([L,R]\) 满足 \(L < l \leq R < r\) 或者 \(l < L \leq r < R\) ,对 \(R\) 为下标维护区间 \(L\) 的最小值,对 \(L\) 为下标维护区间 \(R\) 最大值,两颗线段树即可。

有删除加上线段树分治即可,复杂度是 \(q\log q\log n\) 的。

至于回撤操作,用栈序撤销的线段树即可,写 zkw 的话常数极小,不怕被卡常。代码也很简单。

#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; ++i)
#define drep(i, x, y) for (int i = x; i >= y; --i)
#define ll long long
#define pb push_back
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
using namespace std;
const int N = 200020;
int n, q, lst[N], l[N], r[N], op[N], stk[N], tp; vector<int> T[N << 2];
struct zkw {int t[N << 2], m, tp; pair<int, int> stk[N << 3]; // Min SegTreeinline void build() { m = 1, memset(t, 0x3f, sizeof t); while(m <= n) m <<= 1; }inline void pushup(int p) { t[p] = min(t[p << 1], t[p << 1 | 1]); }inline void up(int p) { while(p >>= 1) pushup(p); }inline void upd(int x, int y) { x += m, stk[++tp] = {x, t[x]}, t[x] = min(t[x], y), up(x); }inline void undo(int lst) { while(tp > lst) t[stk[tp].first] = stk[tp].second, up(stk[tp].first), --tp; }inline int qry(int l, int r) { int res = 1e9; if(l > r) return res;for(l += m - 1, r += m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {if(~l & 1) res = min(res, t[l ^ 1]); if(r & 1) res = min(res, t[r ^ 1]);} return res;}
} t1, t2;
inline void insert(int ql, int qr, int x, int p = 1, int l = 1, int r = q) {if(qr < l || r < ql) return; if(ql <= l && r <= qr) return void(T[p].pb(x));int mid = (l + r) >> 1; insert(ql, qr, x, p << 1, l, mid);insert(ql, qr, x, p << 1 | 1, mid + 1, r); }
inline void Ins(int l, int r) {++tp, stk[tp] = stk[tp - 1];if(t1.qry(l, r - 1) < l) stk[tp] = 1;if(-t2.qry(l + 1, r) > r) stk[tp] = 1;t1.upd(r, l), t2.upd(l, -r);
}
inline void solve(int p = 1, int l = 1, int r = q) {int lst = tp; for(int x : T[p]) Ins(::l[x], ::r[x]);if(l == r) cout << (stk[tp] ? "No\n" : "Yes\n");else { int mid = (l + r) >> 1;solve(p << 1, l, mid), solve(p << 1 | 1, mid + 1, r);} t1.undo(lst), t2.undo(lst), tp = lst;
}
int main() {IOS; cin >> n >> q, t1.build(), t2.build();rep(i, 1, q) {cin >> op[i];if(op[i] == 1) cin >> l[i] >> r[i];else cin >> l[i], lst[l[i]] = i; }rep(i, 1, q) if(op[i] == 1) insert(i, (!lst[i] ? q : lst[i] - 1), i);solve(); return 0;
}

*P10611 故事结局

https://www.luogu.com.cn/problem/P10611

线段树分治是非正解,需要卡常,我还没调出来,但还是提一下

先颜色均摊,然后变成只有加入,删除。

接着以行为下标建立线段树,然后把查询拆成 \(q\log n\) 个。

然后考虑子问题,即区间插入一个数 \(x\),删除之前一次操作,以及区间最值。

两种做法,一种直接标记永久化 + set,一种是再离线一遍然后线段树分治。

\(n,q\) 同阶,总复杂度是 \(n\log^3n\) 的,难以通过。


*CF500F New Year Shopping

https://www.luogu.com.cn/problem/CF500F

线段树分治维护背包板子,复杂度 \(nm\log n+q\)\(m\) 是背包容量。

常数很小直接 300ms 飞过去。

*P3206 [HNOI2010] 城市建设

https://www.luogu.com.cn/problem/P3206

维护动态最小生成树。

两种做法,一种是只保留有用边然后递归下去,细节比较繁琐,一种比较无脑,考虑线段树分治过后只有加边,然后变成 xxxx 经典题,用 LCT 维护生成树即可。

总结

个人认为线段树分治是一个特别实用的技巧,最近的 CF div2 有场的 F 也是线段树分治。在维护连通性时在代码难度上会远低于 LCT ,很多时候代码难度低,用途广,特别适合拿部分分以及辅助解决一些不支持删除的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/904885.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

制作一个简单的带有3D打印部件的四足蜘蛛机器人

在这个项目中,我将向您展示如何使用3D打印部件制作一个简单的4腿行走蜘蛛机器人。该设计主要由上下板、臂接插件、腿和伺服支架五个部分组成。机器人的4条腿由4个手臂部分和4个腿部分组成。机器人的运动总共使用了8个业余伺服电机,4个在手臂上,4个在腿上。在电路方面,首选E…

统计学习之数据挖掘(结构数据)

统计学习之数据挖掘(结构数据):降维聚类关联度分析分类神经网络

2024 腾讯游戏安全大赛 mobile 初赛 wp

找关键结构体 https://www.cnblogs.com/revercc/p/17641855.html 找GWORLD https://bbs.kanxue.com/thread-280042.htm可以发现是 TEXT包裹的,utf-16编码,ida alt + b搜索 53 00 65 00 61 00 6D 00 6C 00 65 00 73 00 73 00 54 00 72 00即可网上翻即可找到 GWorld对应地址: 0…

制造业订单处理烦恼多,日事清 OTD 管理为您排忧解难

你是不是经常因为接单和交货时间差太大而焦头烂额?今天我们就结合制造业OTD管理,带您了解如何应用日事清进行订单交付全周期管理。日事清可以帮你设定精细的流程,从接单到发货,清晰可控地帮你解决以上烦心事。在制造业里打拼,每天都得面对各种烦心事,比如订单处理慢、生产…

如何在SSD1306上显示动态表情符号位图

解锁您的SSD1306上充满活力的视觉效果!学习毫不费力地显示动态表情符号位图,并以风格增强您的项目。 在本教程中,我们将通过使用PCBX在线模拟环境在SSD1306 OLED显示器上显示位图图像的过程。本教程将介绍设置PCBX模拟,格式化位图数据,配置显示大小和管理图像延迟。步骤1:…

redis基础数据结构——ZipList

ZipList 基于特殊写法实现的双端链表,由一系列特殊编码的连续内存块组成,可以像deque一样在双端压入/弹出,并且时间复杂度在O(1) 整体ZL结构如下zlbytes(uint32):当前zl总的byte数。 zltail(uint32):尾结点的offset,指向的是最后一个entry的起始地址。 zllen(uint16):记…

day:28 postman——环境变量(依赖,关联接口)

一.接口的环境变量 (1)定义变量 可以将需要填写的值设为变量 变量设置:{{}}(2)添加环境变量 方法一:方法二:(3)查看环境变量(4)选择环境,执行二.依赖接口 先登录接口成功,生成cookie值,才能让后面接口依赖 cookie值是保持会话 查看cookie值方法 方法一:方法二:…

L1.1 技术和产品准备度

L1.1 技术和产品准备度 技术和产品准备度 技术与产品的演进 ​ 上面这张图展示了如何在技术尚未完全成熟时,启动产品开发,以及技术如何随着新需求或洞察逐步演进,并支持产品的更新换代。产品1.0:由先前研发的的技术3支撑,加上“产品开发可以在预期的技术开发成果的基础上提…

从故障响应到客户信赖:华为ITR流程的五大核心步骤与实战案例

华为究竟是如何在与西方巨头的激烈竞争中崭露头角、脱颖而出的呢?答案是:凭借卓越的服务。今天我们来探讨一下华为是如何通过卓越的服务赢得全球市场的。 一、华为的三件大事 华为前高管费敏曾经总结过,华为的业务可以分成三件大事:1. 开发产品:这就是 IPD 流程,负责从有…

提升生产效率的关键: ethercat转TCPIP智能通信

大家好。最近在数据互联互通方面,我们迎来了一个重要的突破。作为生产管理系统的核心组成部分,数据互联互通一直是一个亟待解决的挑战。我们知道,EtherCAT和TCP/IP是两种不同的通信协议,它们之间的互通性一直存在问题。不过,现在有一款新产品值得关注,这款产品能够实现Et…

Trae初体验

Trae(国际版)的Ai搭载Claude-3.7-Sonnet(完全免费且速度很快)和DeepSeek-R1以及V3(不存在服务器繁忙)以及GPT-4o Trae国服的Ai搭载DouBao和DeepSeek。用Claude-3.7-Sonnet 写一个简易的贪吃蛇小游戏:这个贪吃蛇游戏包含以下功能:使用方向键控制蛇的移动 吃到食物会增加长度和…

C# 从零开始使用Layui.Wpf库开发WPF客户端

一、简介最近需要开发一个桌面版的工具软件,之前用得更多的是Winform,作为一个全干工程师,我们也要兼顾下WPF,趁此机会再研究下开源控件库。MaQaQ:Winform真好用(有个HZHControls控件库,值得一看)。 二、准备工作找了下开源控件库,诸如MaterialDesignInXAML、HandyCon…