题记
真正的水题,然而排位赛的时候不会。这个能算是什么策略呢,按左端点分类,枚举所有区间,维护右区间的最小值就行了。
题意
给出一个排列数组p,包含1到n的数。现在给出一些数对,求不包含这些成对数字的区间有多少。比如p = [1, 3, 2, 4],给出数对(3, 2), (4, 2),则区间(2, 3)包含了数对(3, 2),不可行;区间(4, 2)包含了数对(3, 2)和(4, 2),不可行。区间(1, 2)是可行的。另外,区间长度可以为1,4个单独的区间也是可行的。这就是第一组样例答案为5的分析。
思路
看官方题解得到的思路。
这个最后的处理方法其实和 这道题 有相似的地方,相当于将问题分类成每一个左端点的情况。而这一道题,从右到左枚举每个左端点lf,只要维护一个值rg,使得rg是对应当前左端点能够取到的最大右端点值+1(或者说是不能取到的最小右端点值)。这个左端点能够产生的所有区间数量就是rg - lf。
我们来考虑为什么可以这样得到答案。如果区间i1是不可行的,而区间i2包含区间i1,那么区间i2一定是不可行的。这样就保证了对于lf,大于等于rg的右端点一定不构成合法区间,而当我们把lf向左移动的时候,rg只能变小,不可能变大。
如果区间i2是可行的,而区间i2包含区间i1,那么区间i1一定也是可行的。既然如此,当我们向左移动lf的时候,rg只要不向右移动,那么在左移lf之前的所有约束就可以一并满足,新增加的约束只是当前的lf这个位置所产生的。为了满足它,只需要将rg的取值更新为min(rg, 当前lf位置的点所处的点对中,在lf右边且位置最靠左的位置)。
所以我们预处理的时候,先处理出每一个位置的点a,对于所有点对(a, b),找到那个b的位置大于a,且b最小的位置,把这个位置存在数组中,维护rg的时候使用即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int max_n = 3e5 + 50;
int p[max_n];
int n, m;
int max_right[max_n];
int pos[max_n];
int main() {
//输入n m 初始化max_right为n
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
max_right[i] = n + 1;
}
//输入p序列,处理出每一个元素对应的位置
for (int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
pos[p[i]] = i;
}
//输入点对,记录出每一个位置的max_right
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d %d", &x, &y);
x = pos[x], y = pos[y];
if(x > y) swap(x, y);
max_right[x] = min(max_right[x], y);
}
//从右到左遍历,维护当前左端点对应右端点的最大值,计数答案 rg是记录距离lf最近的右边不能包括进的点
int rg = n + 1;
ll ans = 0;
for (int lf = n; lf >= 1; lf--) {
rg = min(rg, max_right[lf]);
ans += rg - lf;
}
cout << ans << endl;
return 0;
}
反思
关于区间问题,这样枚举左端点的形式,建议自己可以在没有思路的时候尝试往这方面想想。