F. MEX vs MED
F. MEX vs MED
You are given a permutation of length of numbers . Count the number of subsegments of this permutation such that .
of is the smallest non-negative integer that does not occur in . For example:
of the set is the median of the set, i.e. the element that, after sorting the elements in non-decreasing order, will be at position number (array elements are numbered starting from and here denotes rounding down.). For example:
A sequence of numbers is called a permutation if it contains all the numbers from to exactly once.
Input
The first line of the input contains a single integer , the number of test cases.
The descriptions of the test cases follow.
The first line of each test case contains a single integer , the length of the permutation .
The second line of each test case contains exactly integers: , elements of permutation .
It is guaranteed that the sum of over all test cases does not exceed .
Output
For each test case print the answer in a single line: the number of subsegments of this permutation such that .
Example
input
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
output
1 2 4 4 8 8 15 6
Note
The first test case contains exactly one subsegment and on it.
In the third test case, on the following subsegments: , , and , is greater than .
In the fourth test case, on the following subsegments: , , and , greater than .
解题思路
对于这种统计类的问题关键在于如何划分集合使得计数的过程能够做到不重不漏。一般来对于一个序列我们会根据右端点来划分集合,然后枚举右端点来求解满足要求的答案。这里的话可以发现枚举右端点是不行的,因为整个过程的时间复杂度会比较高。
因此这里要选择另外的方式来划分集合。我们根据来划分集合。根据定义,如果某个序列的,那么这个序列必须包含这些值,并且不能包含。因为整个序列是一个排列,因此我们可以记录每个数字的下标位置,然后预处理出所有的数值前缀的最小下标和最大下标,记为和。其中表示数字中最小的下标位置,表示数字中最大的下标位置。因此对于某个,应该满足。
现在要找出所有的区间,看看有多少个区间满足。可以发现在的区间中,必定包含(一共个元素),其余的元素都是严格大于的,如果区间长度不超过,那么这个区间的必定小于,如果区间长度超过,那么必定大于。只要区间的长度不超过,那么就是符合条件的答案。
即现在我们要枚举值,找到包含数字的长度最小的区间(即区间),然后根据左右端点往外延拓,统计包含这个最小长度的区间,且区间长度不超过的区间数目(这里的统计就是根据端点来划分集合了)。这里往外延拓既可以选择枚举右端点求满足条件的左端点数目,也可以枚举左端点求满足条件的右端点数目。
考虑一种情况,如果已经找到了的最小区间,并且,我们都选择枚举右端点求左端点数目。然后又在的左边,此时的最小区间为,继续选择枚举右端点求左端点数目。如果之后的数字都出现在上一个数字的左边,又因为我们每次都从往后枚举,因此时间复杂度会变成。这意味着我们要选择合理的枚举方式。
具体来说是这样的,对于的最小区间,如果,即在区间的右边,这时我们应该枚举右端点求满足条件的左端点数目。如果如果,即在区间的左边,这时我们应该枚举左端点求满足条件的右端点数目。这样我们就可以遍历的过程中不重复地枚举个位置(因为枚举的位置下一次就变成的最小区间,即每次枚举的位置都会变少),即枚举的次数总和就是序列的总长度,因此整个时间复杂度为。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 2e5 + 10; 7 8 int p[N], minp[N], maxp[N]; 9 10 void solve() { 11 int n; 12 scanf("%d", &n); 13 for (int i = 0; i < n; i++) { 14 int x; 15 scanf("%d", &x); 16 p[x] = i; 17 } 18 19 p[n] = n; // mex = n,按理来说n不会出现在序列中,因此我们把n定义在序列之外 20 minp[0] = maxp[0] = p[0]; // 初始时数字0的就是一个区间 21 for (int i = 1; i < n; i++) { 22 minp[i] = min(minp[i - 1], p[i]); // 求数字1~i的最小下标 23 maxp[i] = max(maxp[i - 1], p[i]); // 求数字1~i的最大下标 24 } 25 26 LL ret = 0; 27 for (int i = 1; i <= n; i++) { // mex = 0时,没有满足要求的区间,因此从mex = 1开始 28 if (p[i] >= minp[i - 1] && p[i] <= maxp[i - 1]) continue; // i在最小区间范围内 29 int len = 2 * i; 30 if (p[i] < minp[i - 1]) { // i在最小区间左边 31 for (int j = minp[i - 1]; j > p[i]; j--) { // 枚举左端点 32 if (j + len - 1 < maxp[i - 1]) break; // 区间[j, j + len - 1]没有覆盖最小区间 33 ret += min(n - 1, j + len - 1) - maxp[i - 1] + 1; // 左端点固定,满足条件的右端点范围就在[maxp[i-1], j+len-1] 34 } 35 } 36 else { 37 for (int j = maxp[i - 1]; j < p[i]; j++) { // 枚举左端点 38 if (j - len + 1 > minp[i - 1]) break; // 区间[j - len + 1, j]没有覆盖最小区间 39 ret += minp[i - 1] - max(0, j - len + 1) + 1; // 右端点固定,满足条件的左端点范围就在[j-len+1, minp[i-1]] 40 } 41 } 42 } 43 44 printf("%lld\n", ret); 45 } 46 47 int main() { 48 int t; 49 scanf("%d", &t); 50 while (t--) { 51 solve(); 52 } 53 54 return 0; 55 }
2023-02-13更新:修正了部分错误,贴出一份新AC代码:

#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2e5 + 10; int p[N], minp[N], maxp[N]; void solve() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) { int x; scanf("%d", &x); p[x] = i; } minp[0] = maxp[0] = p[0]; for (int i = 1; i < n; i++) { minp[i] = min(minp[i - 1], p[i]); maxp[i] = max(maxp[i - 1], p[i]); } LL ret = 1; for (int i = 1; i < n; i++) { if (p[i] < minp[i - 1] || p[i] > maxp[i - 1]) { int len = 2 * i; if (p[i] < minp[i - 1]) { for (int j = minp[i - 1]; j > p[i]; j--) { if (j + len - 1 < maxp[i - 1]) break; ret += min(n- 1, j + len - 1) - maxp[i - 1] + 1; } } else { for (int j = maxp[i - 1]; j < p[i]; j++) { if (j - len + 1 > minp[i - 1]) break; ret += minp[i - 1] - max(0, j - len + 1) + 1; } } } } printf("%lld\n", ret); } int main() { int t; scanf("%d", &t); while (t--) { solve(); } return 0; }
参考资料
Codeforces Round #828 (Div. 3) E, F:https://zhuanlan.zhihu.com/p/574230639
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16827677.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效