线段树杂题
2025-09-21

信息转化为主


B - Into Blocks (hard version)

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

  • trick:考察每个颜色出现的区间 \([s_{col},t_{col}]\),对每个 \([s_{col},t_{col})\) 打标记,则没有被打过标记的点后能够进行一次分段。
  • 对于每一段,其代价为段长度 - 众数出现次数。

    这个众数乍一看有点吓人,实际上发现由于每个元素出现的所有位置都在这个段里,所以可以记录每个元素的个数,对于每个元素只钦定其中一个位置来记录即可快速维护。
  • 发现线段树能够比较容易地维护全局查询:维护 \(0\) 位置是经典 trick;且容易发现『段长』之和为 \(n\),故只需维护众数。
  • 实现技巧:单点本身就是自己的最小值,从这一点出发可以规避很多边界情况。

#include <bits/stdc++.h>
const int lim = 2e5;
const int maxn = 2e5 + 5;
struct node {
    int l, r, u, d;
    int lu, ru, mn, mx;
    node operator+ (const node &q) const {
        node res;
        res.d = 0;
        res.l = l, res.r = q.r;
        res.mx = std::max(mx, q.mx);
        res.mn = std::min(mn, q.mn);
        if (mn < q.mn) {
            res.mn = mn, res.u = u;
            res.lu = lu, res.ru = std::max(q.mx, ru);
        }
        else if (q.mn < mn) {
            res.mn = q.mn, res.u = q.u;
            res.lu = std::max(mx, q.lu), res.ru = q.ru;
        }
        else {
            res.lu = lu, res.ru = q.ru;
            res.u = u + q.u + std::max(ru, q.lu);
        }
        return res;
    }
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void pushval(int p, int d) {
    t[p].mn += d, t[p].d += d;
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d), pushval(rt, t[p].d);
    t[p].d = 0;
    return;
}
void upd(int p, int x, int v) {
    if (t[p].l == t[p].r) {
        t[p].u = t[p].mx = v;
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (x <= mid)
        upd(lt, x, v);
    else
        upd(rt, x, v);
    t[p] = t[lt] + t[rt];
    return;
}
void add(int p, int l, int r, int v) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, v);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        add(lt, l, r, v);
    if (r > mid)
        add(rt, l, r, v);
    t[p] = t[lt] + t[rt];
    return;
}
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n, q;
    std::cin >> n >> q;
    std::vector<int> a(n + 1);
    std::vector<std::set<int> > st(lim + 1);
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i];
        st[a[i]].emplace(i);
    }
    bld(1, 1, n);
    for (int i = 1; i <= lim; ++i)
        if (!st[i].empty()) {
            int s = *st[i].begin(), t = *--st[i].end();
            if (s <= t - 1)
                add(1, s, t - 1, 1);
            upd(1, s, (int)st[i].size());
        }
    std::cout << n - ::t[1].u - ::t[1].lu - ::t[1].ru  << '\n';
    auto work = [&](int v, int k) {
        if (st[v].empty())
            return;
        int s = *st[v].begin(), t = *--st[v].end();
        if (s <= t - 1)
            add(1, s, t - 1, k);
        upd(1, s, k == 1 ? (int)st[v].size() : 0);
        return;
    };
    for (int i, x; q--; ) {
        std::cin >> i >> x;
        work(a[i], -1), st[a[i]].erase(i), work(a[i], 1);
        a[i] = x;
        work(a[i], -1), st[a[i]].insert(i), work(a[i], 1);
        std::cout << n - ::t[1].u - ::t[1].lu - ::t[1].ru  << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C - seats 排座位

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

很有价值的一个题

  • 对于一个固定的 \(k\),考虑怎么刻画合法矩形

    发现『前 \(k\) 个元素构成矩形』比『大小为 \(k\) 的矩形包含前 \(k\) 个元素』更合理

  • 考虑前 \(k\) 个元素在满足什么条件时构成矩形

    我最开始的想法是『记录这 \(k\) 个元素占据的最大、最小、最左、最右,如果乘起来为 \(k\),那么合法』

    但会发现这个转化是不好做的,或者说就又往上面那个错误的判定转化(大小为 \(k\) 的矩形包含前 \(k\) 个元素)靠了。两个都有一个共同的问题,就是过于依赖这个 \(k\),限制太具体了,导致不能很好地应用到每个 \(k\)

    现在这个判定的转化比较合理的地方就在于,对矩形本身不存在约束,只是要求构成矩形。这样就允许直接从图形出发,更多地考虑怎样用单点信息合并出矩形与否

  • \([0,k)\) 染成黑色,其他为白色,那么有一个粗暴但简单的方法来刻画『矩形』这个要求:

    图形存在四个『角』。

    再刻画一下『角』:黑点左上 / 左下 / 右上 / 右下两个点都为白。

    当然这样是很荒谬的,因为下图显然不只有四个角。

    所以我们不得不把一个凹角也算作角,不难发现把上方凸角的定义中的主体换成白点即可定义凹角。

  • 优化:算贡献

    维护当前时刻,所有 \(4nm\) 个角在 \(k\) 处是否存在

    此时要用一个类似维护 \(0\) 个数的方法来维护有 \(4\) 个角个数的 \(k\) 个数。

  • 考虑这么一个可能的角:

    其存在当且仅当 \(A\) 为黑,\(B,C\) 为白(凸角),或 \(A\) 为白,\(B,C\) 为黑(凹角)。

    考察 \(A,B,C\) 第一次变黑的时间 \(k_A,k_B,k_C\),那么 \(A\) 能够对所有 \(k\in[k_A,\min\{k_B,k_C\})\)\(k\in[\max\{k_B,k_C\},k_A)\) 造成贡献。

  • 每次修改只会做 \(O(1)\) 次区间加,是可维护的。注意两个被交换的点可能会有公共的相邻点,需要特别处理一下。
  • 这个东西本质上可以看作一个 2⨉2 的Soble 算子,所以可以抠出来一般定义上的角

non-interactive version:

#include <bits/stdc++.h>
const int dir[][2] = { { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 } };
const int maxn = 1e6 + 5;
struct node {
    int l, r, u, d, mn;
    node operator+ (const node &q) const {
        node res;
        res.l = l, res.r = q.r, res.d = 0;
        res.mn = std::min(mn, q.mn), res.u = 0;
        if (mn == res.mn)
            res.u = u;
        if (q.mn == res.mn)
            res.u += q.u;
        return res;
    }
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r, t[p].u = r - l + 1;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void pushval(int p, int v) {
    t[p].mn += v, t[p].d += v;
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d), pushval(rt, t[p].d);
    t[p].d = 0;
    return;
}
void add(int p, int l, int r, int v) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, v);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        add(lt, l, r, v);
    if (r > mid)
        add(rt, l, r, v);
    t[p] = t[lt] + t[rt];
    return;
}
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n, m, q;
    std::cin >> n >> m >> q;
    const int N = n * m;
    std::vector<std::pair<int, int> > pos(N + 1);
    std::vector<std::vector<int> > id(n + 2, std::vector<int> (m + 2, N + 1));
    for (int i = 1, x, y; i <= N; ++i) {
        std::cin >> x >> y, ++x, ++y;
        id[x][y] = i, pos[i] = { x, y };
    }
    bld(1, 1, N);
    auto work = [&](int x, int y, int k) {
        for (int i = 0; i < 2; ++i)
            for (int j = 2; j < 4; ++j) {
                int x1 = x + dir[j][0], y1 = y + dir[i][1];
                if (id[x][y] < std::min(id[x1][y], id[x][y1]))
                    add(1, id[x][y], std::min(id[x1][y], id[x][y1]) - 1, k);
                if (std::max(id[x1][y], id[x][y1]) < id[x][y])
                    add(1, std::max(id[x1][y], id[x][y1]), id[x][y] - 1, k);
            }
        return;
    };
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            work(i, j, 1);
    for (int i1, i2; q--; ) {
        std::cin >> i1 >> i2, ++i1, ++i2;
        auto [x1, y1] = pos[i1];
        auto [x2, y2] = pos[i2];
        std::set<std::pair<int, int> > st;
        st.emplace(x1, y1);
        for (auto [fx, fy] : dir) {
            int x = x1 + fx, y = y1 + fy;
            if (x >= 1 && x <= n && y >= 1 && y <= m)
                st.emplace(x, y);
        }
        st.emplace(x2, y2);
        for (auto [fx, fy] : dir) {
            int x = x2 + fx, y = y2 + fy;
            if (x >= 1 && x <= n && y >= 1 && y <= m)
                st.emplace(x, y);
        }
        for (auto [x, y] : st)
            work(x, y, -1);
        std::swap(id[x1][y1], id[x2][y2]), std::swap(pos[i1], pos[i2]);
        for (auto [x, y] : st)
            work(x, y, 1);
        std::cout << t[1].u << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

interactive version:

#include <bits/stdc++.h>
#ifdef ONLINE_JUDGE
#include "seats.h"
#endif
const int dir[][2] = { { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 } };
const int maxn = 1e6 + 5;
struct node {
    int l, r, u, d, mn;
    node operator+ (const node &q) const {
        node res;
        res.l = l, res.r = q.r, res.d = 0;
        res.mn = std::min(mn, q.mn), res.u = 0;
        if (mn == res.mn)
            res.u = u;
        if (q.mn == res.mn)
            res.u += q.u;
        return res;
    }
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r, t[p].u = r - l + 1;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void pushval(int p, int v) {
    t[p].mn += v, t[p].d += v;
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d), pushval(rt, t[p].d);
    t[p].d = 0;
    return;
}
void add(int p, int l, int r, int v) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, v);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        add(lt, l, r, v);
    if (r > mid)
        add(rt, l, r, v);
    t[p] = t[lt] + t[rt];
    return;
}
int n, m, N;
std::vector<std::pair<int, int> > pos;
std::vector<std::vector<int> > id(n + 2, std::vector<int> (m + 2, N + 1));
void work(int x, int y, int k) {
    for (int i = 0; i < 2; ++i)
        for (int j = 2; j < 4; ++j) {
            int x1 = x + dir[j][0], y1 = y + dir[i][1];
            if (id[x][y] < std::min(id[x1][y], id[x][y1]))
                add(1, id[x][y], std::min(id[x1][y], id[x][y1]) - 1, k);
            if (std::max(id[x1][y], id[x][y1]) < id[x][y])
                add(1, std::max(id[x1][y], id[x][y1]), id[x][y] - 1, k);
        }
    return;
};
void give_initial_chart(int n, int m, std::vector<int> R, std::vector<int> C) {
    ::n = n, ::m = m, N = n * m;
    pos.resize(N + 1);
    id.assign(n + 2, std::vector<int> (m + 2, N + 1));
    for (int i = 0; i < N; ++i) {
        int x = R[i] + 1, y = C[i] + 1;
        id[x][y] = i + 1, pos[i + 1] = { x, y };
    }
    bld(1, 1, N);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            work(i, j, 1);
    return;
}
int swap_seats(int i1, int i2) {
    ++i1, ++i2;
    auto [x1, y1] = pos[i1];
    auto [x2, y2] = pos[i2];
    std::set<std::pair<int, int> > st;
    st.emplace(x1, y1);
    for (auto [fx, fy] : dir) {
        int x = x1 + fx, y = y1 + fy;
        if (x >= 1 && x <= n && y >= 1 && y <= m)
            st.emplace(x, y);
    }
    st.emplace(x2, y2);
    for (auto [fx, fy] : dir) {
        int x = x2 + fx, y = y2 + fy;
        if (x >= 1 && x <= n && y >= 1 && y <= m)
            st.emplace(x, y);
    }
    for (auto [x, y] : st)
        work(x, y, -1);
    std::swap(id[x1][y1], id[x2][y2]), std::swap(pos[i1], pos[i2]);
    for (auto [x, y] : st)
        work(x, y, 1);
    return t[1].u;
}

D - 单旋

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

  • 发现任意时刻参与旋转的都是最值,简单分析可以发现做的这件事情是把最值移到根,其他点相对关系不变,转化为维护深度
  • 考虑新加入点,可以利用 BST 的性质,直接查询 \(x\) 的前驱、后继,则二者存在祖孙关系,把 \(x\) 接在矮的下面即可
  • 考虑做一个先删再加的过程,删会影响儿子的深度(显然只有一侧儿子),加会影响全树深度

E - NEKAMELEONI

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


言论