连续值域区间个数(经典题)
题目大意
RT,求一个排列的连续值域区间个数
数据范围
(1 le n le 10^5)
解题思路
题目描述这么短说明它是一个经典题(也指我不会的那种题)
连续值域区间的性质 (max - min + 1 = len) 即 (max-min-r+l)
对于任意一个区间,总有 (max - min - 1 ge len)
有了这两点性质就可以用单调栈和线段树来做了
具体来说,两个单调栈分别维护最大最小值,线段树维护最小值(val = max - min)及最小值个数
每加一个数进来要整体减一表示 r++,查询当前最小值是否为 0 和其个数即可
代码没必要了
update:补一个不用单调栈的做法
不用单调栈啊,你将 (i) 和 (i + 1,i-1) 连边,那么有 ([l,r]) 生产子图边数 = (r-l)
像上面一样直接线段树即可
update:补一个 cdq 做法
考虑跨过中点的连续值域区间个数,先考虑一种情况,对右边取前缀最大值和前缀最小值,使用桶和队列维护,具体来说,我们强制最小值在左边,最大值在右边,同理我们将序列反过来再跑一遍。另外单独考虑最大值最小值在同一侧的情况
这个给个代码,但不是我的,详见这里
namespace task60 {
int sl[N], sr[N], mn[N], mx[N], cnt[N]; ll ans;
void work(int *a, int *b) {
int la = a[0], lb = b[0];
mn[lb + 1] = 0;
for (int i = 1; i <= lb; i++)
mn[i] = min(mn[i - 1], b[i]), mx[i] = max(mx[i - 1], b[i]);
int curmn = inf, curmx = 0, tl = 1, tr = 1;
for (int i = 1; i <= la; i++) {
curmn = min(curmn, a[i]), curmx = max(curmx, a[i]);
int tmp = curmx - curmn + 1 - i;
if (tmp > 0 && tmp <= lb && mn[tmp] > curmn && mx[tmp] < curmx) ans++;
while (mn[tr] > curmn) cnt[mx[tr] - tr]++, tr++;
while (tl < tr && mx[tl] < curmx) cnt[mx[tl] - tl]--, tl++;
ans += (ll)cnt[curmn + i - 1];
}
for (int i = tl; i < tr; i++) cnt[mx[i] - i] = 0;
}
void cdq(int l, int r) {
if (l == r) { ans++; return; }
int mid = (l + r) >> 1;
sl[0] = mid - l + 1, sr[0] = r - mid;
for (int i = mid; i >= l; i--) sl[mid - i + 1] = p[i];
for (int i = mid + 1; i <= r; i++) sr[i - mid] = p[i];
work(sl, sr), work(sr, sl);
cdq(l, mid), cdq(mid + 1, r);
}
void sol() {
mn[0] = inf, cdq(1, n);
printf("%lld
", ans);
for (int i = 1; i <= n; i++) printf("%d%c", p[i], "
" [i == n]);
}
}