连续值域区间个数(经典题)

连续值域区间个数(经典题)

题目大意

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\n", ans);
		for (int i = 1; i <= n; i++) printf("%d%c", p[i], " \n" [i == n]);
	}
}

你可能感兴趣的:(连续值域区间个数(经典题))