题目描述
难度分:2000
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和一个0~n−1的排列p。
输出p有多少个非空连续子数组b,满足mex(b)>median(b)。
注:mex(b)为不在b中的最小非负整数,例如mex([1,0,3])=2。
注:median(b)为b的中位数,例如median([0,1,2,3])=1。如果有两个中位数,取小的那个。
输入样例
8
1
0
2
1 0
3
1 0 2
4
0 2 1 3
5
3 1 0 2 4
6
2 0 4 1 3 5
8
3 7 2 6 0 1 5 4
4
2 0 1 3
输出样例
1
2
4
4
8
8
15
6
算法
双指针
很有意思的一道题,要想mex(b)>median(b)成立,那么[0,med]都应该在数组b当中。而如果med是b的中位数,那此时已经有med个比med小的数在数组中([0,med)),还需要med或者med+1个比med大的数在数组中才行,这样的话数组b的长度就有两种可能:2×med+1和2×med+2。
因此可以从小到大枚举med∈[0,n),计算在中位数为med的情况下,有多少个数组满足mex(b)>med,而要知道有多少个数组满足这个条件,只需要找到最小的那个数组就好了,这个最小的数组左右边界往外扩张到底的所有数组都符合。
对于一个给定的中位数med,要求数组b要包括这个med(为了快速得到med的位置,可以先预处理出一个pos数组,pos[i]表示i在排列p中的位置),在med∈[0,n)的过程中逐渐扩张左右边界。如果对于中位数med得到的最小数组是[l,r],那么此时数组的左边界left∈[1,l],数组的右边界right∈[r,n],而right根据b的长度有两种可能:right=left+(2×med+1)−1,right=left+(2×med+2)−1。
因此1≤left≤l,r≤left+len−1≤n,得max(1,r−len+1)≤left≤min(l,n−len+1),其中len=2×med+1或2×med+2。med对答案的贡献为min(l,n−len+1)−max(1,r−len+1)+1,med依次取完0到n−1的所有值之后把所有贡献加起来就是最终答案。
复杂度分析
时间复杂度
遍历med∈[0,n)就统计出了答案,而对于每个med,贡献的计算都是O(1),因此整个算法的时间复杂度就是O(n)。
空间复杂度
额外空间主要是pos数组,它的空间消耗是线性的,因此算法的额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 200010;
int t, n, p[N], pos[N];
void solve() {
int left = 1e9, right = 0;
LL ans = 0;
for(int med = 0; med < n; med++) {
// 要包含med这个数必须要获得区间[left,right]
left = min(left, pos[med]), right = max(right, pos[med]);
// 第一种可能的区间长度
int len = 2*med + 2;
if(right - left + 1 <= len) {
int l = max(1, right - len + 1), r = min(left, n - len + 1);
ans += max(0, r - l + 1);
}
// 第二种可能的区间长度
len = 2*med + 1;
if(right - left + 1 <= len) {
int l = max(1, right - len + 1), r = min(left, n - len + 1);
ans += max(0, r - l + 1);
}
}
printf("%lld\n", ans);
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
pos[p[i]] = i;
}
solve();
}
return 0;
}