支配点对小记

支配点对小记

此类问题的形式一般为:多次询问某范围内最优点对(的贡献)。

考虑一些特别的情况,若某点对被严格偏序,显然无需考虑该点对。于是考虑只保留可能成为最优解的点对,称之为支配点对。

对于两个点对 \(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);
    else
        return max(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r));
}
} // namespace SMT

signed 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{dep}(\mathrm{LCA}(l, l + 1, \cdots, r)) = \min_{l \leq i < r} \mathrm{dep}(\mathrm{LCA}(i, i + 1)) \]

考虑对于每个 \(\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);
    else
        update(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);
    else
        return max(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r));
}
} // namespace SMT

inline void solve1() { // l <= ql <= qr <= r
    vector<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 <= qr
    vector<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 <= qr
    vector<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 = 1
    SMT::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 BIT

signed 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);
    else
        return query(ls(x), nl, mid, l, r) + query(rs(x), mid + 1, nr, l, r);
}
} // namespace SMT

signed 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 BIT

namespace 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);
    else
        update(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);
    else
        return max(query(lc[x], nl, mid, l, r), query(rc[x], mid + 1, nr, l, r));
}
} // namespace SMT

inline 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 BIT

signed 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;
}
posted @ 2025-10-31 20:41  wshcl  阅读(25)  评论(0)    收藏  举报