吉司机 + 历史和练习
2025-09-11

并非所有历史和都是吉司机


A - Just Another Game of Stones

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

  • 发现如果选定了这一步取的位置 \(i\),那么要拿的石子是定值 \(a_i-a_i\oplus s\),当 \(a_i\le a_i\oplus s\) 时不合法。
  • 可以用吉司机维护修改和区间异或和,那么询问需要转化。

    答案为 \(a_i>a_i\oplus s\) 的次数,结合异或是不带进位的区间加的观点,考察『加数』\(s\) 的最高位,当且仅当 \(a_i\) 在这一位为 \(1\) 时有贡献。
  • 故再维护一下每一位为 \(1\) 的个数即可。复杂度 \(O(q\log n\log V)\)

#include <bits/stdc++.h>
const int inf = 1 << 30;
const int maxn = 2e5 + 5;
struct {
    std::array<int, 30> u;
    int l, r, cnt, mn, se, d, s;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
int a[maxn];
void pushup(int p) {
    if (t[lt].mn == t[rt].mn) {
        t[p].mn = t[lt].mn, t[p].cnt = t[lt].cnt + t[rt].cnt;
        t[p].se = std::min(t[lt].se, t[rt].se);
    }
    else if (t[lt].mn < t[rt].mn) {
        t[p].mn = t[lt].mn, t[p].cnt = t[lt].cnt;
        t[p].se = std::min(t[lt].se, t[rt].mn);
    }
    else {
        t[p].mn = t[rt].mn, t[p].cnt = t[rt].cnt;
        t[p].se = std::min(t[lt].mn, t[rt].se);
    }
    t[p].s = t[lt].s ^ t[rt].s;
    for (int i = 0; i < 30; ++i)
        t[p].u[i] = t[lt].u[i] + t[rt].u[i];
    return;
}
void bld(int p, int l, int r) {
    t[p].d = -inf;
    t[p].l = l, t[p].r = r;
    if (l == r) {
        for (int i = 0; i < 30; ++i)
            t[p].u[i] = (a[l] >> i) & 1;
        t[p].s = t[p].mn = a[l], t[p].cnt = 1, t[p].se = inf;
        return;
    }
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    pushup(p);
    return;
}
void pushval(int p, int v) {
    if (v > t[p].mn) {
        for (int i = 0; i < 30; ++i) {
            if ((t[p].mn >> i) & 1)
                t[p].u[i] -= t[p].cnt;
            if ((v >> i) & 1)
                t[p].u[i] += t[p].cnt;
        }
        if (t[p].cnt & 1)
            t[p].s ^= t[p].mn ^ v;
        t[p].mn = t[p].d = v;
    }
    return;
}
void pushdown(int p) {
    if (t[p].d != -inf) {
        pushval(lt, t[p].d), pushval(rt, t[p].d);
        t[p].d = -inf;
    }
    return;
}
void add(int p, int l, int r, int v) {
    if (t[p].mn >= v)
        return;
    if (l <= t[p].l && t[p].r <= r && v < t[p].se) {
        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);
    pushup(p);
    return;
}
int askt(int p, int l, int r, int i) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].u[i];
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1, res = 0;
    if (l <= mid)
        res = askt(lt, l, r, i);
    if (r > mid)
        res += askt(rt, l, r, i);
    return res;
}
int asks(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].s;
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1, res = 0;
    if (l <= mid)
        res = asks(lt, l, r);
    if (r > mid)
        res ^= asks(rt, l, r);
    return res;
}
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;
    std::cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    bld(1, 1, n);
    for (int op, l, r, v; m--; ) {
        std::cin >> op >> l >> r >> v;
        if (op == 1)
            add(1, l, r, v);
        else {
            int s = asks(1, l, r) ^ v;
            if (s == 0) {
                std::cout << 0 << '\n';
                continue;
            }
            int t = std::__lg(s);
            std::cout << askt(1, l, r, t) + ((v >> t) & 1) << '\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;
}

B - Prof. Pang’s sequence / C - TEST_90

https://www.luogu.com.cn/problem/P10822 / https://www.luogu.com.cn/problem/P9990

  • 历史和维护子区间问题是一个固定的 trick,只需要对于每一个 \(r\),给合法的 \(l\) 加一。
  • 离线扫描线,对于当前右端点 \(r\),记录每种数最后一次出现的位置 \(pos\)。令 \(r\) 的颜色为 1,从右到左,每碰到一个新的 \(pos\) 就切换颜色
  • 显然对于当前 \(r\),应该被加一的 \(l\) 就是颜色为 1 的这些位置。
  • 考虑更快地维护这个过程,也就是在 \(r\) 的颜色和 \(r-1\) 不同时就可以对 \([1,r)\) 做一次 flip,是可行的
  • 在每个 \(r\) 处查询 \([l,r]\) 的区间和即可。
  • 吐槽:并非所有历史和都是吉司机
#include <bits/stdc++.h>
const int maxn = 5e5 + 5;
struct {
    long long s;
    int l, r, d0, d1, df, ds, u0, u1;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void pushup(int p) {
    t[p].u0 = t[lt].u0 + t[rt].u0;
    t[p].u1 = t[lt].u1 + t[rt].u1;
    return;
}
void pushval(int p, long long v0, long long v1, bool f) {
    if (f) {
        t[p].df ^= 1;
        std::swap(t[p].u0, t[p].u1);
        std::swap(t[p].d0, t[p].d1);
    }
    t[p].d0 += v0, t[p].d1 += v1;
    t[p].s += t[p].u0 * v0 + t[p].u1 * v1;
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d0, t[p].d1, t[p].df);
    pushval(rt, t[p].d0, t[p].d1, t[p].df);
    t[p].d0 = t[p].d1 = t[p].df = 0;
    return;
}
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r;
    t[p].u0 = r - l + 1;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void flip(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, 0, 0, 1);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        flip(lt, l, r);
    if (r > mid)
        flip(rt, l, r);
    pushup(p);
    return;
}
long long ask(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].s;
    pushdown(p);
    auto res(0ll);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        res = ask(lt, l, r);
    if (r > mid)
        res += ask(rt, l, r);
    return res;
}
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;
    std::cin >> n;
    std::vector<int> a(n + 1), la(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    int m;
    std::cin >> m;
    std::vector<long long> res(m + 1);
    std::vector<std::vector<std::pair<int, int> > > u(n + 1);
    for (int i = 1, l, r; i <= m; ++i) {
        std::cin >> l >> r;
        u[r].emplace_back(l, i);
    }
    bld(1, 1, n);
    for (int r = 1; r <= n; ++r) {
        flip(1, la[a[r]] + 1, r);
        pushval(1, 0, 1, 0);
        for (auto [l, id] : u[r])
            res[id] = ask(1, l, r);
        la[a[r]] = r;
    }
    for (int i = 1; i <= m; ++i)
        std::cout << res[i] << '\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;
}

D - Stations

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

  • 不太能直接维护 \(b\),但发现如果维护每个 \(i\) 最远能到达的点,发现每次更新 \(j\) 会把 \(j\) 左边的所有点值和 \(j-1\) 取 min。
  • 发现吉司机能够在取 min 的同时在另一个树状树组里维护差分
  • 需要注意更新的时机,应该是每次线段树更新函数访问到完整节点时。
  • 复杂度 \(O(q\log^2n)\)。但真的吗?单点修改似乎对势能有影响?然而并不会相关分析。

看起来有点激进了,没想到确实是正解。

可以发现树状数组在时间上确实是有优势的

#include <bits/stdc++.h>
const int maxn = 2e5 + 5;
const int inf = 0x3f3f3f3f;
struct {
    int l, r, mx, se, d, cnt;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
int n;
long long bit[maxn][2];
int lowbit(int x) { return x & -x; }
void add(int x, long long v) {
    for (int i = x; i <= n; i += lowbit(i))
        bit[i][0] += v, bit[i][1] += v * (x - 1);
    return;
}
long long ask(int x) {
    auto res = 0ll;
    for (int i = x; i; i -= lowbit(i))
        res += bit[i][0] * x - bit[i][1];
    return res;
}
void pushup(int p) {
    if (t[lt].mx == t[rt].mx) {
        t[p].mx = t[lt].mx, t[p].cnt = t[lt].cnt + t[rt].cnt;
        t[p].se = std::max(t[lt].se, t[rt].se);
    }
    else if (t[lt].mx > t[rt].mx) {
        t[p].mx = t[lt].mx, t[p].cnt = t[lt].cnt;
        t[p].se = std::max(t[lt].se, t[rt].mx);
    }
    else {
        t[p].mx = t[rt].mx, t[p].cnt = t[rt].cnt;
        t[p].se = std::max(t[lt].mx, t[rt].se);
    }
    return;
}
void bld(int p, int l, int r) {
    t[p].d = inf;
    t[p].l = l, t[p].r = r;
    if (l == r) {
        add(l, 1), add(l + 1, -1);
        t[p].mx = l, t[p].se = -1, t[p].cnt = 1;
        return;
    }
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    pushup(p);
    return;
}
void pushval(int p, int v) {
    if (v <= t[p].mx)
        t[p].mx = t[p].d = v;
    return;
}
void pushdown(int p) {
    if (t[p].d != inf) {
        pushval(lt, t[p].d), pushval(rt, t[p].d);
        t[p].d = inf;
    }
    return;
}
void add(int p, int l, int r, int v) {
    if (t[p].mx <= v)
        return;
    if (l <= t[p].l && t[p].r <= r && v > t[p].se) {
        add(t[p].mx + 1, t[p].cnt);
        add(v + 1, -t[p].cnt);
        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);
    pushup(p);
    return;
}
void upd(int p, int x, int v) {
    if (t[p].l == t[p].r) {
        add(t[p].mx + 1, 1), add(v + 1, -1);
        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);
    pushup(p);
    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 m;
    std::cin >> n >> m;
    bld(1, 1, n);
    for (int op; m--; ) {
        std::cin >> op;
        if (op == 1) {
            int x, v;
            std::cin >> x >> v;
            if (x != 1)
                add(1, 1, x - 1, x - 1);
            upd(1, x, v);
            // for (int i = 1; i <= n; ++i)
            //     std::cout << ask(i) - ask(i - 1) << ' ';
            // std::cout << '\n';
        }
        else {
            int l, r;
            std::cin >> l >> r;
            std::cout << ask(r) - ask(l - 1) << '\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;
}

E - LuoTianyi and the Function

https://codeforces.com/problemset/problem/1824/D

  • 由于 \(j\) 和函数关联更强,考虑把 \(j\) 这一维扫描线 + 差分掉
  • 经典套路,维护每个 \(x\) 从右向左第一次出现的位置 \(pos_x\)
  • 每次 \(j\) 移动一下,会更新一段区间(可能不存在)的 \(g\) 值,它们原本为 \(pos_{a_j}\),需要被更新为下一个 \(pos\)
  • 记录值为 \(pos_x\) 的区间,做区间赋值历史和即可。
#include <bits/stdc++.h>
const int maxn = 1e6 + 5;
struct {
    int l, r;
    long long d1, d2, d3, u, s;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void pushup(int p) {
    t[p].u = t[lt].u + t[rt].u;
    t[p].s = t[lt].s + t[rt].s;
    return;
}
void pushval(int p, long long d1, long long d2, long long d3) {
    t[p].s += t[p].u * d2 + d3 * (t[p].r - t[p].l + 1);
    t[p].d3 += d3;
    if (t[p].d1)
        t[p].d3 += t[p].d1 * d2;
    else
        t[p].d2 += d2;
    if (d1) {
        t[p].u = d1 * (t[p].r - t[p].l + 1);
        t[p].d1 = d1;
    }
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d1, t[p].d2, t[p].d3);
    pushval(rt, t[p].d1, t[p].d2, t[p].d3);
    t[p].d1 = t[p].d2 = t[p].d3 = 0ll;
    return;
}
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r;
    if (l == r)
        return;
    int mid = (t[p].l + t[p].r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void upd(int p, int l, int r, long long v) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, v, 0ll, 0ll);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        upd(lt, l, r, v);
    if (r > mid)
        upd(rt, l, r, v);
    pushup(p);
    return;
}
long long ask(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].s;
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    auto res = 0ll;
    if (l <= mid)
        res = ask(lt, l, r);
    if (r > mid)
        res += ask(rt, l, r);
    return res;
}
signed 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), la(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    struct query { int l, r, id, k; };
    std::vector<long long> res(q + 1);
    std::vector<std::vector<query> > u(n + 1);
    for (int i = 1, l, r, x, y; i <= q; ++i) {
        std::cin >> l >> r >> x >> y;
        if (x != 1)
            u[x - 1].push_back({ l, r, i, -1 });
        u[y].push_back({ l, r, i, 1 });
    }
    bld(1, 1, n);
    std::vector<int> pre(n + 2), nex(n + 2), l(n + 2);
    pre[n + 1] = 0, nex[0] = n + 1;
    for (int i = 1; i <= n; ++i) {
        int p = la[a[i]];
        nex[pre[n + 1]] = i, pre[i] = pre[n + 1], pre[n + 1] = i, nex[i] = n + 1;
        l[i] = i;
        if (p != 0) {
            upd(1, l[p], p, nex[p]);
            l[nex[p]] = l[p];
            pre[nex[p]] = pre[p], nex[pre[p]] = nex[p];
        }
        upd(1, i, i, i);
        pushval(1, 0, 1, 0);
        for (auto [l, r, id, k] : u[i])
            res[id] += k * ask(1, l, r);
        la[a[i]] = i;
    }
    for (int i = 1; i <= q; ++i)
        std::cout << res[i] << '\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;
}

F - Yunli’s Subarray Queries (extreme version)

https://codeforces.com/problemset/problem/2009/G3

  • 考虑对 \(\forall i\),令 \(a_i\gets a_i-i\),那么能够参与同一个连续子段的元素值应相同
  • 显然对于任意一个长度为 \(k\) 的子段,其答案为 \(k\) 减去众数出现次数(\([l, r]\) 处答案记为 \(g_r\)

    这一点可以用类似莫队的方法来做
  • 那么对于更长的子段 \([l, r]\),其 \(f\) 就是 \(\min\limits_{l+k-1\le i\le r}\{g_i\}\)

    原问题中的一次 \([l, r]\) 的询问就可以转化为 \(\sum\limits_{i=l+k-1}^r\sum\limits_{j=i}^r\min\{ g_{i\sim j}\}\)
  • 这里有一个 trick:扫描线时可以用单调栈的想法来维护最小值操作,那么就把最值操作转化为一般的区间加历史和了。

#include <bits/stdc++.h>
const int maxn = 1e6 + 5;
struct {
    int l, r;
    long long d1, d2, d3, u, s;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void pushup(int p) {
    t[p].u = t[lt].u + t[rt].u;
    t[p].s = t[lt].s + t[rt].s;
    return;
}
void pushval(int p, long long d1, long long d2, long long d3) {
    t[p].s += t[p].u * d2 + d3 * (t[p].r - t[p].l + 1);
    t[p].d3 += d3 + t[p].d1 * d2;
    t[p].d2 += d2;
    t[p].u += d1 * (t[p].r - t[p].l + 1);
    t[p].d1 += d1;
    return;
}
void pushdown(int p) {
    pushval(lt, t[p].d1, t[p].d2, t[p].d3);
    pushval(rt, t[p].d1, t[p].d2, t[p].d3);
    t[p].d1 = t[p].d2 = t[p].d3 = 0ll;
    return;
}
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r;
    t[p].u = t[p].s = 0ll;
    t[p].d1 = t[p].d2 = t[p].d3 = 0;
    if (l == r)
        return;
    int mid = (t[p].l + t[p].r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void add(int p, int l, int r, long long v) {
    if (l <= t[p].l && t[p].r <= r) {
        pushval(p, v, 0ll, 0ll);
        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);
    pushup(p);
    return;
}
long long ask(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].s;
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    auto res = 0ll;
    if (l <= mid)
        res = ask(lt, l, r);
    if (r > mid)
        res += ask(rt, l, r);
    return res;
}
signed 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 T;
    for (std::cin >> T; T--; ) {
        int n, k, q;
        std::cin >> n >> k >> q;
        std::vector<int> a(n + 1);
        for (int i = 1; i <= n; ++i)
            std::cin >> a[i], a[i] -= i;
        bld(1, 1, n);
        std::vector<int> g(n + 1);
        std::unordered_map<int, int> cnt;
        std::multiset<int> t;
        for (int i = 1; i <= n; ++i) {
            if (i > k) {
                t.erase(t.find(cnt[a[i - k]]));
                if (--cnt[a[i - k]])
                    t.insert(cnt[a[i - k]]);
            }
            if (cnt[a[i]])
                t.erase(t.find(cnt[a[i]]));
            t.insert(++cnt[a[i]]);
            if (i >= k)
                g[i] = k - *--t.end();
        }
        std::vector<long long> res(q + 1);
        std::vector<std::vector<std::pair<int, int> > > u(n + 1);
        for (int i = 1, l, r; i <= q; ++i) {
            std::cin >> l >> r;
            u[r].emplace_back(l + k - 1, i);
        }
        std::stack<std::pair<int, int> > st;
        for (int i = 1; i <= n; ++i) {
            std::pair<int, int> now(g[i], i);
            add(1, i, i, g[i]);
            for (; !st.empty() && st.top() > now; st.pop()) {
                auto [v, p] = st.top();
                add(1, p, now.second - 1, g[i] - v);
                now.second = p;
            }
            st.push(now);
            pushval(1, 0ll, 1ll, 0ll);
            for (auto [l, id] : u[i])
                res[id] = ask(1, l, i);
        }
        for (int i = 1; i <= q; ++i)
            std::cout << res[i] << '\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;
}

言论