QOJ 8229 栈
如果直接维护序列上栈的信息试着做一下会发现非常困难,因为这些操作不是能用较为简单的 tag 刻画的。
但是注意到查询的实际上是单点的信息,于是可以考虑换维:
扫描序列轴,用数据结构维护时间轴。
这时对于两个修改的操作都可以差分为 \(l\) 处加入和 \(c + 1\) 处删去了。
于是修改和询问都变成了在某一个下标加入的操作,而不是要在一个区间都加入的操作了。
于是接下来就可以考虑如何用数据结构维护单点修改和前缀查询(查询的实际上是这之前所有时刻发生的操作)。
首先对于这个入栈弹栈的操作看着并不优美,考虑刻画成一些更好看的形式。
因为弹栈弹走的就是最顶上的元素,也就是最近的元素。
于是可以考虑把入栈操作看作是加入带权的 \(\texttt{(}\),对于出栈操作看作是加入 \(\texttt{)}\),并且让能够匹配上的 \(\texttt{(}\) 权值变为 \(0\),那么最后 \(\texttt{(}\) 的权值和就是答案。
那么进一步的,因为匹配上的括号都没有贡献了,不妨直接把匹配上的括号扔掉。
所以此时能够知道这个括号串的形态一定形如 \(\texttt{)))}\cdots\texttt{(((}\)。
于是就可以考虑直接用 \([sum, cl, cr]\) 表示一个括号串的形态,\(sum\) 代表 \(\texttt{(}\) 的权值和,\(cl\) 代表 \(\texttt{)}\) 的数量,\(cr\) 代表 \(\texttt{(}\) 的数量。
于是一个想法是分块,对于查询可以从后往前合并状态,可以做到 \(\mathcal{O}(q\sqrt{q}\log q)\),但并不是本文的重点所以略过。
依然考虑用线段树维护区间的信息。
但是在这题遇到了一个困难是左右儿子信息不能直接合并,因为 \(\texttt{(}\) 有着不同的权值,对于右儿子给到的 \(\texttt{)}\) 在抵消的过程中抵消的权值是不一样的,就不能直接得到增量。
但是考虑到 \(\texttt{)}\) 抵消掉的一定是一个后缀,所以可以考虑用单侧递归线段树来维护。
具体来说,定义 \(\operatorname{calc}(k, c)\) 代表此时在 \(k\) 节点,右边有 \(c\) 个 \(\texttt{)}\) 要抵消,节点 \(k\) 处的 \([sum, \Delta cl, cr]\),注意这里是 \(\Delta cl\),也就是在合并后的 \(cl'\) 减掉 \(c\) 的差值,这是为了方便左右信息相加。
接下来对于维护三个信息,定义 \(tl_{ls_k} = \operatorname{calc}(ls_k, cl(rs_k))\),即 \(ls_k\) 在得到 \(rs_k\) 的 \(\texttt{)}\) 后得到的信息的增量,那就可以定义出 \(t_k = tl_{ls_k} + t_{rs_k}\) 代表 \(k\) 这个子树的信息(这就是把 \(\operatorname{calc}\) 的 \(cl\) 定义成 \(\Delta cl\) 的原因)。
那么接下来考虑求解这个 \(\operatorname{calc}(k, c)\),首先分讨:
- \(k\) 是叶子节点。
那么直接讨论 \(cr(k)\) 和 \(c\) 的大小关系:- 若 \(c\ge cr(k)\),那么所有 \(\texttt{(}\) 都会被抵消掉,返回 \([0, cl(k) - cr(k), 0]\)。
- 若 \(c < cr(k)\),那么所有右边传来的 \(\texttt{)}\) 都会被抵消掉,返回 \([(cr(k) - c)\times v, -c, cr(k) - c]\)。
- \(k\) 不是叶子节点。
那么也分类讨论,因为 \(c\) 抵消的是个后缀,看只抵消到了 \(rs_k\) 还是把 \(rs_k\) 抵消完了继续到了 \(ls_k\):- 若 \(c\ge cr(rs_k)\),那么就说明 \(rs_k\) 被抵消完了并且继续向 \(ls_k\) 抵消了,返回 \(\operatorname{calc}(ls_k, c + cl(rs_k) - cr(rs_k)) + [0, cl(rs_k) - cr(rs_k), 0]\)。
- 若 \(c < cr(rs_k)\),那么就说明只抵消到了 \(rs_k\),\(ls_k\) 依然只由 \(rs_k\) 的 \(\texttt{)}\) 抵消,所以返回 \(tl_{ls_k} + \operatorname{calc}(rs_k, c)\)。
于是就可以在 \(\mathcal{O}(\log m)\) 的复杂度求得一个节点的信息,就可以在 \(\mathcal{O}(\log^2 m)\) 的复杂度处理出一次修改了。
接下来考虑询问,对于询问的前缀首先可以拆分成 \(\mathcal{O}(\log m)\) 个区间。
那么就可以像上面一样,从后往前扫区间,维护当前的信息 \([sum, cl, cr]\),那么通过 \(\operatorname{calc}(k, cl)\) 就可以知道当前信息对 \(k\) 节点的影响,也可以求出来对信息的增量,那么就可以直接合并了。
对于查询的 \([p, q]\),首先可以差分成 \([1, p - 1]\) 和 \([1, q]\)。
假设要得到 \([1, r]\) 的信息,那么首先可以通过询问得到的 \(\texttt{(}\) 的个数 \(r'\),那么接下来假设在后面加上 \(\max(r' - r, 0)\) 个 \(\texttt{)}\),就可以抵消掉第 \(r\) 个 \(\texttt{(}\) 后面的 \(\texttt{(}\),就得到了 \([1, r]\) 的信息了。
最后时间复杂度为 \(\mathcal{O}(n + m\log^2 m)\)。
代码并不难写。
#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 1e5 + 10;
int n, m;
ll vl[maxn], val[maxn * 4];
struct node_t {
ll sum, cl, cr;
inline node_t operator + (const node_t &b) const {
return node_t{sum + b.sum, cl + b.cl, cr + b.cr};
}
} tr[maxn * 4], trl[maxn * 4];
bool isleaf[maxn * 4];
inline void build(int k = 1, int l = 1, int r = m) {
tr[k] = {0, 0, 0};
if (l == r) {
isleaf[k] = true, val[k] = vl[l];
return ;
}
int mid = l + r >> 1;
build(k << 1, l, mid), build(k << 1 | 1, mid + 1, r);
}
node_t calc(int k, ll c) {
if (isleaf[k]) {
if (c >= tr[k].cr) {
return node_t{0, tr[k].cl - tr[k].cr, 0};
} else {
return node_t{(tr[k].cr - c) * val[k], -c, tr[k].cr - c};
}
}
if (c >= tr[k << 1 | 1].cr) {
return calc(k << 1, c - tr[k << 1 | 1].cr + tr[k << 1 | 1].cl) + node_t{0, tr[k << 1 | 1].cl - tr[k << 1 | 1].cr, 0};
} else {
return trl[k << 1] + calc(k << 1 | 1, c);
}
}
inline void update(int t, ll x, int k = 1, int l = 1, int r = m) {
if (l == r) {
if (val[k]) tr[k].sum += x * val[k], tr[k].cr += x;
else tr[k].cl += x;
return ;
}
int mid = l + r >> 1;
if (t <= mid) update(t, x, k << 1, l, mid);
else update(t, x, k << 1 | 1, mid + 1, r);
trl[k << 1] = calc(k << 1, tr[k << 1 | 1].cl), tr[k] = trl[k << 1] + tr[k << 1 | 1];
}
inline void query(int R, node_t &ans, int k = 1, int l = 1, int r = m) {
if (r <= R) {
node_t now = calc(k, ans.cl);
ans = ans + now;
return ;
}
int mid = l + r >> 1;
if (R > mid) query(R, ans, k << 1 | 1, mid + 1, r);
query(R, ans, k << 1, l, mid);
}
struct opt {
int op, t; ll x;
};
std::vector<opt> q[maxn];
ll ans[maxn];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
ans[i] = -1;
int op; scanf("%d", &op);
if (op == 1) {
int l, r, x;
scanf("%d%d%d%lld", &l, &r, &x, &vl[i]);
q[l].push_back({0, i, x});
q[r + 1].push_back({0, i, -x});
} else if (op == 2) {
int l, r; ll w;
scanf("%d%d%lld", &l, &r, &w);
q[l].push_back({0, i, w});
q[r + 1].push_back({0, i, -w});
} else {
ans[i] = 0;
int k; ll a, b;
scanf("%d%lld%lld", &k, &a, &b);
q[k].push_back({1, i, b});
q[k].push_back({-1, i, a - 1});
}
}
build();
for (int i = 1; i <= n; i++) {
for (auto [op, t, x] : q[i]) {
if (op == 0) {
update(t, x);
} else {
node_t res = {0, 0, 0};
query(t, res);
res.sum = 0, res.cl = std::max(res.cr - x, 0ll), res.cr = 0;
query(t, res);
ans[t] += res.sum * op;
}
}
}
for (int i = 1; i <= m; i++) {
if (ans[i] != -1) printf("%lld\n", ans[i]);
}
return 0;
}
浙公网安备 33010602011771号