支配点对小记
此类问题的形式一般为:多次询问某范围内最优点对(的贡献)。
考虑一些特别的情况,若某点对被严格偏序,显然无需考虑该点对。于是考虑只保留可能成为最优解的点对,称之为支配点对。
对于两个点对 \(a, b\) ,考虑 \(a\) 一定不劣于 \(b\) 的条件:\(b\) 若合法,则 \(a\) 一定合法,且 \(a\) 更优。
在此基础上,我们希望这个严格不劣于的条件尽量弱,这样就可以排除更多点对,简化问题。
P11392 [JOI Open 2019] 三级跳 / Triple Jump
给定 \(a_{1 \sim n}\) ,\(q\) 次询问,每次给出 \(l, r\) ,求满足 \(l \le x < y < z \le r\) 且 \(y - x \le z - y\) 的三元组 \((x, y, z)\) 中 \(a_x + a_y + a_z\) 的最大。
\(n, q \le 5 \times 10^5\)
首先可以发现固定 \(x, y\) 时,最优的 \(z\) 是一个后缀最大值,因此无需保留 \(z\) 。
由于 \(z\) 的选择范围和 \(y - x\) 有关, \(y - x\) 越大则可选范围越小。对于两个点对 \((x_1, y_1), (x_2, y_2)\) ,若 \(x_1 \leq x_2 < y_2 \leq y_1\) 且 \(a_{x_2} + a_{y_2} \ge a_{x_1} + a_{y_1}\) ,则 \((x_1, y_1)\) 被 \((x_2, y_2)\) 偏序。形式化的,若 \((l, r)\) 为支配点对,则不存在 \(x \in (l, r)\) 满足 \(a_x \geq \min(a_l, a_r)\) 。
分析一下支配点对的数量,设 \(L_i, R_i\) 表示 \(i\) 左右最近的 \(\geq a_i\) 的位置,则所有 \([L_i, i], [i, R_i]\) 即为支配点对,即支配点对的数量只有 \(2n\) 个。
离线扫描线即可做到 \(O((n + q) \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7;vector<pair<int, int> > qry[N];
vector<int> upd[N];int a[N], sta[N], ans[N];int n, q;namespace SMT {
int mx[N << 2], ans[N << 2], tag[N << 2];inline int ls(int x) {return x << 1;
}inline int rs(int x) {return x << 1 | 1;
}inline void spread(int x, int k) {ans[x] = max(ans[x], mx[x] + k), tag[x] = max(tag[x], k);
}inline void pushdown(int x) {if (tag[x])spread(ls(x), tag[x]), spread(rs(x), tag[x]), tag[x] = 0;
}void build(int x, int l, int r) {if (l == r) {mx[x] = ans[x] = a[l];return;}int mid = (l + r) >> 1;build(ls(x), l, mid), build(rs(x), mid + 1, r);ans[x] = mx[x] = max(mx[ls(x)], mx[rs(x)]);
}void update(int x, int nl, int nr, int l, int r, int k) {if (l <= nl && nr <= r) {spread(x, k);return;}pushdown(x);int mid = (nl + nr) >> 1;if (l <= mid)update(ls(x), nl, mid, l, r, k);if (r > mid)update(rs(x), mid + 1, nr, l, r, k);ans[x] = max(ans[ls(x)], ans[rs(x)]);
}int query(int x, int nl, int nr, int l, int r) {if (l <= nl && nr <= r)return ans[x];pushdown(x);int mid = (nl + nr) >> 1;if (r <= mid)return query(ls(x), nl, mid, l, r);else if (l > mid)return query(rs(x), mid + 1, nr, l, r);elsereturn max(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r));
}
} // namespace SMTsigned main() {scanf("%d", &n);for (int i = 1; i <= n; ++i)scanf("%d", a + i);for (int i = 1, top = 0; i <= n; ++i) {while (top && a[sta[top]] < a[i])--top;if (top)upd[sta[top]].emplace_back(i);sta[++top] = i;}for (int i = n, top = 0; i; --i) {while (top && a[sta[top]] < a[i])--top;if (top)upd[i].emplace_back(sta[top]);sta[++top] = i;}SMT::build(1, 1, n);scanf("%d", &q);for (int i = 1; i <= q; ++i) {int l, r;scanf("%d%d", &l, &r);qry[l].emplace_back(r, i);}for (int i = n; i; --i) {for (int it : upd[i])if (it * 2 - i <= n)SMT::update(1, 1, n, it * 2 - i, n, a[i] + a[it]);for (auto it : qry[i])ans[it.second] = SMT::query(1, 1, n, i, it.first);}for (int i = 1; i <= q; ++i)printf("%d\n", ans[i]);return 0;
}
P11364 [NOIP2024] 树上查询
给定一棵 \(n\) 个点的树,\(q\) 次询问,每次给出 \(l, r, k\) ,求:
\[\max_{l \le l' \le r' \le r \and r' - l' + 1 \ge k} \mathrm{dep}(\mathrm{LCA}(l, l + 1, \cdots, r)) \]\(n, q \le 5 \times 10^5\)
首先可以发现:
考虑对于每个 \(\mathrm{LCA}(i, i + 1)\) ,找到 LCA 等于它的极长区间 \([l, r]\) 满足 \(l \le i < i + 1 \le r\) ,称其为 \(i\) 的支配区间,这不难对 \(\mathrm{dep}(\mathrm{LCA}(i, i + 1))\) 做单调栈求出。
问题转化为每次对于一个给出的区间,求与其交集 \(\geq k\) 的支配区间中的最深深度。考虑分讨对询问 \([l, r]\) 产生贡献的支配区间 \([l', r']\) :
- \(l \le l' \le r \le r'\) :保证 \(r' - l' + 1 \ge k\) 时,限制条件即为 \(l' \le r - k + 1\) 。
- \(l' \le l \le r' \le r\) :保证 \(r' - l' + 1 \ge k\) 时,限制条件即为 \(r' \ge l + k - 1\) 。
- \(l \le l' \le r' \le r\) :保证 \(r' - l' + 1 \ge k\) 时,算前两种的时候就会算到。由于是最优化问题,前面两种将其重复统计也没事。
- \(l' \le l \le r \le r'\) :升序扫描左端点,则扫到 \(l\) 时所有 \(l' = l\) 的支配区间都被加入 DS,每次查询 \(r' \ge r\) 的最大深度即可。
不难发现限制条件都形如二维偏序,离线扫描线即可做到 \(O((n + q) \log n)\) 。
注意特殊处理 \(k = 1\) 的情况,此时答案为区间深度最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7, LOGN = 19;struct Graph {vector<int> e[N];inline void insert(int u, int v) {e[u].emplace_back(v);}
} G;struct Query {int l, r, k;
} qry[N];int fa[N][LOGN], dep[N], d[N], sta[N], L[N], R[N], ans[N];int n, q;void dfs(int u, int f) {fa[u][0] = f, dep[u] = dep[f] + 1;for (int i = 1; i < LOGN; ++i)fa[u][i] = fa[fa[u][i - 1]][i - 1];for (int v : G.e[u])if (v != f)dfs(v, u);
}inline int LCA(int x, int y) {if (dep[x] < dep[y])swap(x, y);for (int h = dep[x] - dep[y]; h; h &= h - 1)x = fa[x][__builtin_ctz(h)];if (x == y)return x;for (int i = LOGN - 1; ~i; --i)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];return fa[x][0];
}namespace SMT {
int mx[N << 2];inline int ls(int x) {return x << 1;
}inline int rs(int x) {return x << 1 | 1;
}inline void pushup(int x) {mx[x] = max(mx[ls(x)], mx[rs(x)]);
}void build(int x, int l, int r) {mx[x] = 0;if (l == r)return;int mid = (l + r) >> 1;build(ls(x), l, mid), build(rs(x), mid + 1, r);
}void update(int x, int nl, int nr, int pos, int k) {if (nl == nr) {mx[x] = max(mx[x], k);return;}int mid = (nl + nr) >> 1;if (pos <= mid)update(ls(x), nl, mid, pos, k);elseupdate(rs(x), mid + 1, nr, pos, k);pushup(x);
}int query(int x, int nl, int nr, int l, int r) {if (l <= nl && nr <= r)return mx[x];int mid = (nl + nr) >> 1;if (r <= mid)return query(ls(x), nl, mid, l, r);else if (l > mid)return query(rs(x), mid + 1, nr, l, r);elsereturn max(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r));
}
} // namespace SMTinline void solve1() { // l <= ql <= qr <= rvector<vector<int> > upd(n + 1), ask(n + 1);for (int i = 1; i < n; ++i)upd[L[i]].emplace_back(i);for (int i = 1; i <= q; ++i)ask[qry[i].l].emplace_back(i);SMT::build(1, 1, n);for (int i = 1; i <= n; ++i) {for (auto it : upd[i])SMT::update(1, 1, n, R[it], d[it]);for (int it : ask[i])ans[it] = max(ans[it], SMT::query(1, 1, n, qry[it].r, n));}
}inline void solve2() { // l <= ql <= r <= qr and ql <= l <= r <= qrvector<vector<int> > upd(n + 1), ask(n + 1);for (int i = 1; i < n; ++i)upd[R[i] - L[i] + 1].emplace_back(i);for (int i = 1; i <= q; ++i)ask[qry[i].k].emplace_back(i);SMT::build(1, 1, n);for (int i = n; i; --i) {for (auto it : upd[i])SMT::update(1, 1, n, R[it], d[it]);for (int it : ask[i])ans[it] = max(ans[it], SMT::query(1, 1, n, qry[it].l + qry[it].k - 1, qry[it].r));}
}inline void solve3() { // ql <= l <= qr <= r and ql <= l <= r <= qrvector<vector<int> > upd(n + 1), ask(n + 1);for (int i = 1; i < n; ++i)upd[R[i] - L[i] + 1].emplace_back(i);for (int i = 1; i <= q; ++i)ask[qry[i].k].emplace_back(i);SMT::build(1, 1, n);for (int i = n; i; --i) {for (auto it : upd[i])SMT::update(1, 1, n, L[it], d[it]);for (int it : ask[i])ans[it] = max(ans[it], SMT::query(1, 1, n, qry[it].l, qry[it].r - qry[it].k + 1));}
}inline void solve4() { // k = 1SMT::build(1, 1, n);for (int i = 1; i <= n; ++i)SMT::update(1, 1, n, i, dep[i]);for (int i = 1; i <= q; ++i)if (qry[i].k == 1)ans[i] = max(ans[i], SMT::query(1, 1, n, qry[i].l, qry[i].r));
}signed main() {scanf("%d", &n);for (int i = 1; i < n; ++i) {int u, v;scanf("%d%d", &u, &v);G.insert(u, v), G.insert(v, u);}dfs(1, 0);for (int i = 1; i < n; ++i)d[i] = dep[LCA(i, i + 1)];for (int i = 1, top = 0; i < n; ++i) {while (top && d[sta[top]] >= d[i])--top;L[i] = top ? sta[top] + 1 : 1, sta[++top] = i;}for (int i = n - 1, top = 0; i; --i) {while (top && d[sta[top]] >= d[i])--top;R[i] = top ? sta[top] : n, sta[++top] = i;}scanf("%d", &q);for (int i = 1; i <= q; ++i)scanf("%d%d%d", &qry[i].l, &qry[i].r, &qry[i].k);solve1(), solve2(), solve3(), solve4();for (int i = 1; i <= q; ++i)printf("%d\n", ans[i]);return 0;
}
P7880 [Ynoi2006] rldcot
给定一棵 \(n\) 个点的树,\(m\) 次询问,每次给出 \(l, r\) ,求 \(|\{\mathrm{dep}(\mathrm{LCA}(i, j)) \mid l \le i \le j \le r \}|\) 。
\(n \le 10^5\) ,\(m \le 5 \times 10^5\)
对于每个点 \(u\) ,定义 \(u\) 的支配点对 \((i, j)\) 为满足 \(\mathrm{LCA}(i, j) = u\) 且不存在 \(i \le i' \le j' \le j\) 满足 \(\mathrm{LCA}(i', j') = u\) 的点对。
支配点对可以用启发式合并取出,每个点维护 set ,每次枚举小子树的点,在大子树里找前驱后继即可,不难发现支配点对的数量级是 \(O(n \log n)\) 的。
询问直接扫描线,时间复杂度 \(O(n \log^2 n + m \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, M = 5e5 + 7;struct Graph {vector<pair<int, int> > e[N];inline void insert(int u, int v, int w) {e[u].emplace_back(v, w);}
} G;vector<pair<int, int> > upd[N], qry[N];
set<int> st[N];ll dis[N];
int fa[N], dep[N], lst[N], ans[M];int n, m;void dfs1(int u, int f) {fa[u] = f, dep[u] = dep[f] + 1;for (auto it : G.e[u]) {int v = it.first, w = it.second;if (v == f)continue;dis[v] = dis[u] + w, dfs1(v, u);}
}void dfs2(int u) {st[u].emplace(u), upd[u].emplace_back(u, dis[u]);for (auto it : G.e[u]) {int v = it.first;if (v == fa[u])continue;dfs2(v);if (st[v].size() > st[u].size())swap(st[u], st[v]);for (int it : st[v]) {if (*st[u].begin() < it)upd[it].emplace_back(*prev(st[u].lower_bound(it)), dis[u]);if (it < *st[u].rbegin())upd[*st[u].upper_bound(it)].emplace_back(it, dis[u]);}for (int it : st[v])st[u].emplace(it);}
}namespace BIT {
int c[N];inline void update(int x, int k) {for (; x; x -= x & -x)c[x] += k;
}inline int query(int x) {int res = 0;for (; x <= n; x += x & -x)res += c[x];return res;
}
} // namespace BITsigned main() {scanf("%d%d", &n, &m);for (int i = 1; i < n; ++i) {int u, v, w;scanf("%d%d%d", &u, &v, &w);G.insert(u, v, w), G.insert(v, u, w);}dfs1(1, 0);vector<ll> vec;for (int i = 1; i <= n; ++i)vec.emplace_back(dis[i]);sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end());for (int i = 1; i <= n; ++i)dis[i] = lower_bound(vec.begin(), vec.end(), dis[i]) - vec.begin();dfs2(1);for (int i = 1; i <= m; ++i) {int l, r;scanf("%d%d", &l, &r);qry[r].emplace_back(l, i);}for (int i = 1; i <= n; ++i) {for (auto it : upd[i])if (it.first > lst[it.second])BIT::update(lst[it.second], -1), BIT::update(it.first, 1), lst[it.second] = it.first;for (auto it : qry[i])ans[it.second] = BIT::query(it.first);}for (int i = 1; i <= m; ++i)printf("%d\n", ans[i]);return 0;
}
P8528 [Ynoi2003] 铃原露露
给定 \(a_{1 \sim n}\) 和一棵树,\(m\) 次询问,每次给出 \(l, r\) ,求有多少个 \([l, r]\) 的子区间 \([l', r']\) 满足对于任意 \(l' \le a_i \le a_j \le r'\) 均满足 \(l' \le a_{\mathrm{LCA}(i, j)} \le r'\) 。
\(n, m \le 2 \times 10^5\)
类似上一题求出支配点对,每次覆盖不合法的区间。问题转化为区间加、区间求 \(0\) 作为最小值的数量。
扫描线维护历史和即可,时间复杂度 \(O(n \log^2 n + m \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;struct Graph {vector<int> e[N];inline void insert(int u, int v) {e[u].emplace_back(v);}
} G;set<int> st[N];
vector<tuple<int, int, int> > upd[N];
vector<pair<int, int> > qry[N];ll ans[N];
int a[N], fa[N];int n, m;inline void insert(int x, int y, int z) {if (z < x)upd[y].emplace_back(z + 1, x, 1);else if (z > y)upd[y].emplace_back(1, x, 1), upd[z].emplace_back(1, x, -1);
}void dfs(int u) {st[u].emplace(a[u]);for (int v : G.e[u]) {dfs(v);if (st[u].size() < st[v].size())swap(st[u], st[v]);for (int x : st[v]) {if (*st[u].begin() < x)insert(*prev(st[u].lower_bound(x)), x, a[u]);if (x < *st[u].rbegin())insert(x, *st[u].lower_bound(x), a[u]);}for (int x : st[v])st[u].emplace(x);}
}namespace SMT {
ll s[N << 2];
int mn[N << 2], cnt[N << 2], tag1[N << 2], tag2[N << 2];inline int ls(int x) {return x << 1;
}inline int rs(int x) {return x << 1 | 1;
}inline void pushup(int x) {mn[x] = min(mn[ls(x)], mn[rs(x)]), s[x] = s[ls(x)] + s[rs(x)];cnt[x] = (mn[ls(x)] == mn[x] ? cnt[ls(x)] : 0) + (mn[rs(x)] == mn[x] ? cnt[rs(x)] : 0);
}inline void spread1(int x, int k) {tag1[x] += k, mn[x] += k;
}inline void spread2(int x, int k) {tag2[x] += k, s[x] += 1ll * k * cnt[x];
}inline void pushdown(int x) {if (tag1[x])spread1(ls(x), tag1[x]), spread1(rs(x), tag1[x]), tag1[x] = 0;if (tag2[x]) {if (mn[ls(x)] == mn[x])spread2(ls(x), tag2[x]);if (mn[rs(x)] == mn[x])spread2(rs(x), tag2[x]);tag2[x] = 0;}
}void build(int x, int l, int r) {cnt[x] = r - l + 1;if (l == r)return;int mid = (l + r) >> 1;build(ls(x), l, mid), build(rs(x), mid + 1, r);
}void update(int x, int nl, int nr, int l, int r, int k) {if (l <= nl && nr <= r) {spread1(x, k);return;}pushdown(x);int mid = (nl + nr) >> 1;if (l <= mid)update(ls(x), nl, mid, l, r, k);if (r > mid)update(rs(x), mid + 1, nr, l, r, k);pushup(x);
}void modify(int x, int nl, int nr, int l, int r) {if (l <= nl && nr <= r) {if (!mn[x])spread2(x, 1);return;}pushdown(x);int mid = (nl + nr) >> 1;if (l <= mid)modify(ls(x), nl, mid, l, r);if (r > mid)modify(rs(x), mid + 1, nr, l, r);pushup(x);
}ll query(int x, int nl, int nr, int l, int r) {if (l <= nl && nr <= r)return s[x];pushdown(x);int mid = (nl + nr) >> 1;if (r <= mid)return query(ls(x), nl, mid, l, r);else if (l > mid)return query(rs(x), mid + 1, nr, l, r);elsereturn query(ls(x), nl, mid, l, r) + query(rs(x), mid + 1, nr, l, r);
}
} // namespace SMTsigned main() {scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++i)scanf("%d", a + i);for (int i = 2; i <= n; ++i)scanf("%d", fa + i), G.insert(fa[i], i);dfs(1);for (int i = 1; i <= m; ++i) {int l, r;scanf("%d%d", &l, &r);qry[r].emplace_back(l, i);}SMT::build(1, 1, n);for (int i = 1; i <= n; ++i) {for (auto it : upd[i])SMT::update(1, 1, n, get<0>(it), get<1>(it), get<2>(it));SMT::modify(1, 1, n, 1, i);for (auto it : qry[i])ans[it.second] = SMT::query(1, 1, n, it.first, i);}for (int i = 1; i <= m; ++i)printf("%lld\n", ans[i]);return 0;
}
CF765F Souvenirs
类似的题目:CF1793F Rebrending / P5926 [JSOI2009] 面试的考验
给定 \(a_{1 \sim n}\) ,\(m\) 次询问,每次给出 \(l, r\) ,求 \(\min_{l \le i < j \le r} |a_i - a_j|\) 。
\(n \le 10^5\) ,\(m \le 3 \times 10^5\)
离线询问,固定右端点 \(i\) ,考虑 \(j < i\) 且 \(a_j > a_i\) 的贡献,\(a_j < a_i\) 的贡献只要翻转值域后再做一次即可。
用权值线段树维护 \(j < i\) 且 \(a_j > a_i\) 的值,每次暴力找到这些 \(j\) 更新答案,但是这样复杂度至少为平方。
考虑当前能产生贡献的位置 \(j\) ,若下一次找到的 \(k\) 可以更新答案,则必然有 \(a_k - a_i < a_j - a_k\) ,两边同时减去 \(a_i\) 并移项 \(a_k - a_i < \frac{1}{2}(a_j - a_i)\) 。由于每次差值减半,因此每个位置只有 \(O(\log V)\) 个支配点对,每次找满足条件的最靠后的点对即可,最靠后是为了尽可能让更多区间包住该店对。
剩下就是二维数点状物,时间复杂度 \(O(n \log^2 V + q \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 7, Q = 3e5 + 7;vector<pair<int, int> > qry[N];int a[N], ans[Q];int n, q;namespace BIT {
int c[N];inline void update(int x, int k) {for (; x; x -= x & -x)c[x] = min(c[x], k);
}inline int query(int x) {int res = inf;for (; x <= n; x += x & -x)res = min(res, c[x]);return res;
}
} // namespace BITnamespace SMT {
const int S = N << 5;int lc[S], rc[S], mx[S];int tot, root;inline int newnode() {return ++tot, lc[tot] = rc[tot] = mx[tot] = 0, tot;
}void update(int &x, int nl, int nr, int p, int k) {if (!x)x = newnode();mx[x] = max(mx[x], k);if (nl == nr)return;int mid = (nl + nr) >> 1;if (p <= mid)update(lc[x], nl, mid, p, k);elseupdate(rc[x], mid + 1, nr, p, k);
}int query(int x, int nl, int nr, int l, int r) {if (!x)return 0;if (l <= nl && nr <= r)return mx[x];int mid = (nl + nr) >> 1;if (r <= mid)return query(lc[x], nl, mid, l, r);else if (l > mid)return query(rc[x], mid + 1, nr, l, r);elsereturn max(query(lc[x], nl, mid, l, r), query(rc[x], mid + 1, nr, l, r));
}
} // namespace SMTinline void solve() {memset(BIT::c + 1, inf, sizeof(int) * n);SMT::tot = SMT::root = 0;for (int i = 1; i <= n; ++i) {for (int p = SMT::query(SMT::root, 0, inf, a[i], inf); p; p = SMT::query(SMT::root, 0, inf, a[i], (a[i] + a[p] - 1) / 2))BIT::update(p, a[p] - a[i]);SMT::update(SMT::root, 0, inf, a[i], i);for (auto it : qry[i])ans[it.second] = min(ans[it.second], BIT::query(it.first));}
}signed main() {scanf("%d", &n);for (int i = 1; i <= n; ++i)scanf("%d", a + i);scanf("%d", &q);for (int i = 1; i <= q; ++i) {int l, r;scanf("%d%d", &l, &r);qry[r].emplace_back(l, i), ans[i] = inf;}solve();for (int i = 1; i <= n; ++i)a[i] = inf - a[i];solve();for (int i = 1; i <= q; ++i)printf("%d\n", ans[i]);return 0;
}
CF1677E Tokitsukaze and Beautiful Subsegments
给定排列 \(a_{1 \sim n}\) ,称区间 \([l, r]\) 合法当且仅当存在 \(l \le i < j \le r\) 满足 \(a_i \times a_j = \max_{k = l}^r a_k\) 。
\(q\) 次询问,每次给出 \(l, r\) ,求 \([l, r]\) 的合法子区间数量。
\(n \le 2 \times 10^5\) ,\(q \le 10^6\)
枚举 \(a_i = \max\) ,记 \(L_i, R_i\) 表示位置 \(i\) 左右第一个 \(> a_i\) 的位置。考虑所有二元组 \((x, y)\) 满足 \(x \times y = a_i\) ,则会对所有 \(l \in (L_i, \min(x, i)], r \in [\max(y, i), R_i)\) 的区间产生贡献。显然二元组数量为 \(O(n \log n)\) 级别,问题转化为矩形覆盖、矩形求和。
矩形覆盖并不好做,考虑进一步分析矩形的性质。注意到两个矩形有交,它们的 \(a_i = \max\) 一定是同一个(区间最大值一定),因此只要枚举 \(a_i\) 的时候做好去重即可。首先显然可以去掉相互包含的区间,然后排序后只要对相邻区间去重即可。
时间复杂度 \(O(n \log^2 n + q \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7, Q = 1e6 + 7;vector<tuple<int, int, int> > upd[N];
vector<pair<int, int> > qry[N];ll ans[Q];
int a[N], p[N], sta[N], L[N], R[N];int n, q;struct BIT {ll c0[N], c1[N];inline void modify(int x, int k) {for (int i = x; i <= n; i += i & -i)c1[i] += k, c0[i] -= 1ll * (x - 1) * k;}inline void update(int l, int r, int k) {modify(l, k), modify(r + 1, -k);}inline ll ask(int x) {ll res = 0;for (int i = x; i; i -= i & -i)res += c1[i] * x + c0[i];return res;}inline ll query(int l, int r) {return ask(r) - ask(l - 1);}
} A, B;signed main() {scanf("%d%d", &n, &q);for (int i = 1; i <= n; ++i)scanf("%d", a + i), p[a[i]] = i;for (int i = 1, top = 0; i <= n; ++i) {while (top && a[sta[top]] < a[i])--top;L[i] = top ? sta[top] : 0, sta[++top] = i;}for (int i = n, top = 0; i; --i) {while (top && a[sta[top]] < a[i])--top;R[i] = top ? sta[top] : n + 1, sta[++top] = i;}for (int i = 1; i <= n; ++i) {vector<pair<int, int> > vec;for (int j = 1; j * j < a[i]; ++j) {if (a[i] % j)continue;int l = p[j], r = p[a[i] / j];if (l > r)swap(l, r);if (L[i] < l && r < R[i])vec.emplace_back(min(l, i), max(r, i));}sort(vec.begin(), vec.end(), [](const auto &a, const auto &b) {return a.first == b.first ? a.second > b.second : a.first < b.first;});vector<pair<int, int> > now = {make_pair(L[i], i - 1)};for (auto it : vec) {while (!now.empty() && it.second <= now.back().second)now.pop_back();now.emplace_back(it);}for (int j = 1; j < now.size(); ++j) {upd[now[j].second].emplace_back(now[j - 1].first + 1, now[j].first, 1);upd[R[i]].emplace_back(now[j - 1].first + 1, now[j].first, -1);}}for (int i = 1; i <= q; ++i) {int l, r;scanf("%d%d", &l, &r);qry[r].emplace_back(l, i);}for (int i = 1; i <= n; ++i) {for (auto it : upd[i]) {int l = get<0>(it), r = get<1>(it), k = get<2>(it);A.update(l, r, k), B.update(l, r, -1ll * k * (i - 1));}for (auto it : qry[i])ans[it.second] = A.query(it.first, i) * i + B.query(it.first, i);}for (int i = 1; i <= q; ++i)printf("%lld\n", ans[i]);return 0;
}
P9058 [Ynoi2004] rpmtdq
双倍经验:P9678 [ICPC 2022 Jinan R] Tree Distance
给定一棵 \(n\) 个点的带边权树,\(m\) 次询问,每次给出 \(l, r\) ,求 \(\min_{l \le i < j \le r} \mathrm{dist}(i, j)\) 。
\(n \le 2 \times 10^5\) ,\(q \le 10^6\)
树上距离问题考虑点分治,一次处理连通块时设 \(d_x\) 为 \(x\) 到重心的距离,则每次只要对连通块内的点求支配点对即可。其中一个点对 \((x, y)\) 的贡献为 \(d_x + d_y\) ,由于是求 \(\mathrm{dist}\) 的最小值,而 \(d_x + d_y \ge \mathrm{dist}(x, y)\) ,因此这是对的。
定义支配点对为满足 \(\max(d_x, d_y) < \min_{i = x + 1}^{y - 1} a_i\) 的点对 \((x, y)\) ,可以发现这样定义的话区间是无法缩小某一端的,且显然可以覆盖到答案。
接下来考虑求解支配点对,升序按 \(d\) 枚举点 \(p\) ,则 \(p\) 之前的点中 \(p\) 的前驱后继都可以与 \(p\) 构成支配点对。于是可以在 \(O(n \log^2 n)\) 的时间内求出支配点对,并且由此可以分析出支配点对的数量是 \(O(n \log n)\) 级别的。
但是这样做会被卡常,因为 set 常数太大。注意到该过程实际上等价于将点按编号排序后正反各做一次单调栈,如此做常数就会小很多。
剩下就是二维点对状物,扫描线不难处理,时间复杂度 \(O(n \log^2 n + q \log n)\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7, Q = 1e6 + 7;struct Graph {vector<pair<int, int> > e[N];inline void insert(int u, int v, int w) {e[u].emplace_back(v, w);}
} G;vector<pair<int, ll> > upd[N];
vector<pair<int, int> > qry[N];
vector<int> vec;ll dis[N], ans[Q];
int siz[N], mxsiz[N], sta[N];
bool vis[N];int n, q, root;int getsiz(int u, int f) {siz[u] = 1;for (auto it : G.e[u]) {int v = it.first;if (!vis[v] && v != f)siz[u] += getsiz(v, u);}return siz[u];
}void getroot(int u, int f, int Siz) {siz[u] = 1, mxsiz[u] = 0;for (auto it : G.e[u]) {int v = it.first;if (!vis[v] && v != f)getroot(v, u, Siz), siz[u] += siz[v], mxsiz[u] = max(mxsiz[u], siz[v]);}mxsiz[u] = max(mxsiz[u], Siz - siz[u]);if (!root || mxsiz[u] < mxsiz[root])root = u;
}void dfs(int u, int f) {vec.emplace_back(u);for (auto it : G.e[u]) {int v = it.first, w = it.second;if (!vis[v] && v != f)dis[v] = dis[u] + w, dfs(v, u);}
}inline void calc() {sort(vec.begin(), vec.end());int top = 0;for (int it : vec) {while (top && dis[sta[top]] > dis[it])--top;if (top)upd[it].emplace_back(sta[top], dis[it] + dis[sta[top]]);sta[++top] = it;}reverse(vec.begin(), vec.end()), top = 0;for (int it : vec) {while (top && dis[sta[top]] > dis[it])--top;if (top)upd[sta[top]].emplace_back(it, dis[it] + dis[sta[top]]);sta[++top] = it;}
}void solve(int u) {vis[u] = true, dis[u] = 0, vec = {u};for (auto it : G.e[u]) {int v = it.first, w = it.second;if (!vis[v])dis[v] = dis[u] + w, dfs(v, u);}calc();for (auto it : G.e[u]) {int v = it.first;if (!vis[v])root = 0, getroot(v, u, getsiz(v, u)), solve(root);}
}namespace BIT {
ll c[N];inline void update(int x, ll k) {for (; x; x -= x & -x)c[x] = min(c[x], k);
}inline ll query(int x) {ll res = inf;for (; x <= n; x += x & -x)res = min(res, c[x]);return res;
}
} // namespace BITsigned main() {scanf("%d", &n);for (int i = 1; i < n; ++i) {int u, v, w;scanf("%d%d%d", &u, &v, &w);G.insert(u, v, w), G.insert(v, u, w);}root = 0, getroot(1, 0, n), solve(root);scanf("%d", &q);for (int i = 1; i <= q; ++i) {int l, r;scanf("%d%d", &l, &r);qry[r].emplace_back(l, i);}memset(BIT::c + 1, 0x3f, sizeof(ll) * n);for (int i = 1; i <= n; ++i) {for (auto it : upd[i])BIT::update(it.first, it.second);for (auto it : qry[i])ans[it.second] = BIT::query(it.first);}for (int i = 1; i <= q; ++i)printf("%lld\n", ans[i] == inf ? -1 : ans[i]);return 0;
}