线段树时间分治
线段树时间分治解决的问题是一类可离线的,要求支持先修改后撤销,查询某一时间点前修改的总贡献的问题。大致的思路是,在时间轴上建一棵线段树,把
以洛谷模板题 P5787 二分图 /【模板】线段树分治 为例:有一张
“先出现后消失”可以转换成
我们把这个操作“加”到线段树上去。具体写法是在线段树的每个节点上开一个 vector,然后
void insert(int p, int l, int r, int x) {
if (l <= t[p].l && t[p].r <= r) {
t[p].v.emplace_back(x); // x是修改操作的编号
return;
}
if (l <= mid) insert(ls, l, r, x);
if (r > mid) insert(rs, l, r, x);
}
把所有的修改放上线段树之后,每个节点的 vector 中存储的就是所有在整个这个时间段内存在的边。此时可以从上到下遍历整个线段树,并维护一个扩展域并查集。当走到一个时间段,把这个节点上的边加入并查集,发现出现了矛盾,则这个时间段内一定不是二分图,不需要再向下递归了,直接把这个区间全部标记为 No
;否则继续向下递归。回溯的时候,需要撤销当前时间段内加上的边,可以使用可撤销化并查集(用栈记录合并,复原现场)。因为使用了可撤销化,不能路径压缩,必须按秩合并保证复杂度。
下面是遍历部分的代码:
int find(int x) { // 路径压缩会丢失父亲信息,导致无法撤销
while (x != fa[x]) x = fa[x];
return x;
}
stack<pair<int, int>> memo;
void merge(int x, int y) {
if (x == y) return;
if (d[x] > d[y]) swap(x, y); // 按秩合并:树高小的往大的上面合并(连到根节点)
memo.emplace(x, d[x] == d[y]); // 记录合并
fa[x] = y;
d[y] += d[x] == d[y]; // 如果树高相等,总树高+1
}
void dfs(int p, int l, int r) {
int siz = memo.size();
for (auto i : t[p].v) {
int x = find(u[i]), y = find(v[i]);
if (x == y) { // 如果出现矛盾(两个节点属于同一部)
for (int j = l; j <= r; j++) cout << "No\n";
goto DRAW_BACK;
}
merge(x, find(v[i] + n));
merge(y, find(u[i] + n));
}
if (l == r) {
cout << "Yes\n";
} else {
dfs(ls, l, mid);
dfs(rs, mid + 1, r);
}
DRAW_BACK:
while (memo.size() > siz) { // 把这一部分新加的边全部撤销掉
auto [x, v] = memo.top();
memo.pop();
d[find(x)] -= v, fa[x] = x;
}
}
// main()
for (int i = 1; i <= n * 2; i++) fa[i] = i;
dfs(1, 1, k);
今年辽宁省赛的 K 题可重集合也是线段树分治,当时被卡住不会处理错失前三,现在可以补了。题目要求维护一个可重集合,支持元素的增加和删除,在每一次操作后输出集合中元素通过加法组合能生成多少个不同的正整数。
注意到可以背包 dp,设
因为有删除操作,所以上线段树分治。根据插入删除的时间确定每个元素在时间轴上存在的区间,递归处理即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
const int M = 5e5 + 10; // x[i]的上界
int n, x[N], ans[N];
unordered_map<int, vector<int>> s;
bitset<M> dp;
struct segtree {
int l, r;
vector<int> v;
} t[N << 2];
#define ls p << 1
#define rs p << 1 | 1
#define mid ((t[p].l + t[p].r) >> 1)
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) return;
build(ls, l, mid), build(rs, mid + 1, r);
}
void insert(int p, int l, int r, int x) {
if (l <= t[p].l && t[p].r <= r) {
t[p].v.emplace_back(x);
return;
}
if (l <= mid) insert(ls, l, r, x);
if (r > mid) insert(rs, l, r, x);
}
void dfs(int p, int l, int r) {
bitset<M> tmp = dp; // 记录状态
for (int i : t[p].v) dp |= dp << i; // bitset优化背包
if (l == r) {
ans[l] = dp.count() - 1; // 有多少个1就是有多少个数能表示(-1减去0)
dp = tmp;
return;
}
dfs(ls, l, mid);
dfs(rs, mid + 1, r);
dp = tmp; // 回溯
}
int main() {
cin.tie(0)->ios::sync_with_stdio(0);
cin >> n;
build(1, 1, n);
for (int i = 1, op; i <= n; i++) {
cin >> op >> x[i];
if (op == 1) {
s[x[i]].emplace_back(i); // 记录可重集内所有x[i]的下标(起点)
} else {
insert(1, s[x[i]].back(), i - 1, x[i]); // 遇到删除操作,把到这为止的一段加进去
s[x[i]].pop_back();
}
}
for (auto& [i, v] : s) {
if (v.empty()) continue;
for (int j : v) insert(1, j, n, i); // 没有删除的,直接覆盖到最后
}
dp[0] = 1; // 初始化,0一定能被表示
dfs(1, 1, n);
for (int i = 1; i <= n; i++) {
cout << ans[i] << '\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具