判断子区间是连续&计算子区间连续的个数(排列)

题意
给定一个长度为 n 的排列,询问有多少个子区间内部值域连续。

解法思路

  1. 问题分析:
    • 子区间 [l, r] 内部值域连续的条件是 max - min == r - l

    • 转化为数学表达式:max - min + l - r == 0

    • 目标是快速统计满足该条件的子区间数量。

  2. 扫描线算法:
    • 枚举右端点 r,用线段树维护每个左端点 lrmax - min + l - r

    • 对于每个 r,需要动态更新 maxmin 的影响范围(通过单调栈实现)。

    • 线段树维护区间最小值及其出现次数,用于统计 max - min + l - r >= 0 的情况。

  3. 非排列扩展:
    • 如果不是排列,可以将条件改为 max - min + 1 - cnt >= 0,其中 cnt 是区间内不同数字的个数。

    • 需要额外维护前一个相同数字的位置来更新 cnt

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;

#define dbg(x...) \
do { \
    cout << #x << " -> "; \
    err(x); \
} while (0)

void err() {
    cout << endl << endl;
}

template<class T, class... Ts>
void err(T arg, Ts... args) {
    cout << fixed << setprecision(10) << arg << ' ';
    err(args...);
}

struct SegmentTree {
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
    vector<int> tr, tag, cnt;
    void init(int n) {
        tr.assign(n * 20, 0);
        tag.assign(n * 20, 0);
        cnt.assign(n * 20, 0);
    }
    void build(int p, int l, int r) {
        if (l == r) {
            tr[p] = 0;
            cnt[p] = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(ls(p), l, mid);
        build(rs(p), mid + 1, r);
        push_up(p);
    }
    void push_down(int p, int l, int r) {
        tag[ls(p)] += tag[p];
        tag[rs(p)] += tag[p];
        tr[ls(p)] += tag[p];
        tr[rs(p)] += tag[p];
        tag[p] = 0;
    }
    void push_up(int p) {
        tr[p] = min(tr[ls(p)], tr[rs(p)]);
        cnt[p] = 0;
        if (tr[p] == tr[ls(p)]) cnt[p] += cnt[ls(p)];
        if (tr[p] == tr[rs(p)]) cnt[p] += cnt[rs(p)];
    }
    void update(int p, int l, int r, int ml, int mr, int x) {
        if (l >= ml && mr >= r) {
            tr[p] += x;
            tag[p] += x;
            return;
        }
        push_down(p, l, r);
        int mid = (l + r) >> 1;
        if (ml <= mid) update(ls(p), l, mid, ml, mr, x);
        if (mr > mid) update(rs(p), mid + 1, r, ml, mr, x);
        push_up(p);
    }
    int query(int p, int l, int r, int ml, int mr) {
        int ans = 0;
        if (l >= ml && mr >= r) {
            if (tr[p] == 0) return cnt[p];
            else return 0;
        }
        push_down(p, l, r);
        int mid = (l + r) >> 1;
        if (ml <= mid) ans += query(ls(p), l, mid, ml, mr);
        if (mr > mid) ans += query(rs(p), mid + 1, r, ml, mr);
        return ans;
    }
};

void solve() {
    int n; cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        int x, y; cin >> x >> y;
        a[x] = y;
    }
    SegmentTree tr;
    tr.init(n);
    tr.build(1, 1, n);
    stack<pair<int, int>> mx_sta, mi_sta;
    for (int i = 1; i <= n; i++) {
        tr.update(1, 1, n, i, i, i);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (mx_sta.empty()) {
            mx_sta.push({a[i], i});
        } else {
            while (!mx_sta.empty() && mx_sta.top().first < a[i]) {
                int r = mx_sta.top().second;
                int val = mx_sta.top().first;
                mx_sta.pop();
                int l = 1;
                if (!mx_sta.empty()) l = mx_sta.top().second + 1;
                tr.update(1, 1, n, l, r, -val);
                tr.update(1, 1, n, l, r, a[i]);
            }
            mx_sta.push({a[i], i});
        }
        if (mi_sta.empty()) {
            mi_sta.push({a[i], i});
        } else {
            while (!mi_sta.empty() && mi_sta.top().first > a[i]) {
                int r = mi_sta.top().second;
                int val = mi_sta.top().first;
                mi_sta.pop();
                int l = 1;
                if (!mi_sta.empty()) l = mi_sta.top().second + 1;
                tr.update(1, 1, n, l, r, val);
                tr.update(1, 1, n, l, r, -a[i]);
            }
            mi_sta.push({a[i], i});
        }
        if (i - 1 >= 1) tr.update(1, 1, n, 1, i - 1, (i - 1));
        tr.update(1, 1, n, 1, i, -i);
        ans += tr.query(1, 1, n, 1, i);
    }
    cout << ans;
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--) solve();
    return 0;
}

异或哈希
解决两类问题:

  1. 判断序列是否相同:
    • 维护区间 [l, r] 的异或和,判断两个区间的异或和是否相等。

    • 扩展值域到 2^64 以降低异或为 0 的冲突概率。

  2. 判断数字出现 k 次:
    • 判断一个数字出现 k 的倍数次,即异或和为 0。

    • 对于非二进制 k 进制,模拟二进制运算法则,将每一位结果相加,确保新异或为 0 时出现次数是 k 的倍数。

    • 对于询问出现恰好 k 次的序列时,我们首先考虑一个序列里的数字出现k的倍数次只要在新异或下满足异或和为 0 ,然后对区间做一个双指针维护单个数字最多出现 k 次即可.

posted @ 2025-05-19 20:38  sword1e1  阅读(85)  评论(0)    收藏  举报