判断子区间是连续&计算子区间连续的个数(排列)
题意
给定一个长度为 n 的排列,询问有多少个子区间内部值域连续。
解法思路
-
问题分析:
• 子区间[l, r]内部值域连续的条件是max - min == r - l。• 转化为数学表达式:
max - min + l - r == 0。• 目标是快速统计满足该条件的子区间数量。
-
扫描线算法:
• 枚举右端点r,用线段树维护每个左端点l到r的max - min + l - r。• 对于每个
r,需要动态更新max和min的影响范围(通过单调栈实现)。• 线段树维护区间最小值及其出现次数,用于统计
max - min + l - r >= 0的情况。 -
非排列扩展:
• 如果不是排列,可以将条件改为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;
}
异或哈希
解决两类问题:
-
判断序列是否相同:
• 维护区间[l, r]的异或和,判断两个区间的异或和是否相等。• 扩展值域到
2^64以降低异或为 0 的冲突概率。 -
判断数字出现
k次:
• 判断一个数字出现k的倍数次,即异或和为 0。• 对于非二进制
k进制,模拟二进制运算法则,将每一位结果相加,确保新异或为 0 时出现次数是k的倍数。• 对于询问出现恰好
k次的序列时,我们首先考虑一个序列里的数字出现k的倍数次只要在新异或下满足异或和为 0 ,然后对区间做一个双指针维护单个数字最多出现k次即可.

浙公网安备 33010602011771号