题目描述
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。
输入描述
第一行为两个正整数n和b ,第二行为1~n 的排列。
输出描述
输出一个整数,即中位数为b的连续子序列个数。
示例1
输入
7 4
5 7 2 4 3 1 6
输出
4
备注
对于30%的数据中,满足 n≤100;
对于60%的数据中,满足 n≤1000;
对于100%的数据中,满足 n≤100000,1≤b≤n。
思路:
- 题目限定了序列的长度为奇数,那么这个中位数一定在这个1~n中
- 一个序列的中位数,表示从小到大排序之后,位于中间位置的数,那么一定可以得到:小于中位数的个数等于大于中位数的个数
- 把1~n中小于中位数b的数修改为-1, 大于中位数b的数修改为1
- 如何判断一个序列中的中位数是b呢?在这个序列中,1和-1的个数相同,也就是序列的和减去中位数b为0
- 计算有多少个连续子序列的中位数是b:先找到中位数b在1~n中对应的下标
- 从n-1开始遍历,一直遍历到左端点,用sum来记录一个连续子序列的右端点是中位数b的子序列的和,如果当前sum为0,表示以当前遍历的这个数为左端点,中位数b为右端点的连续子序列的中位数是b
- 在遍历中位数左侧区间的时候,我们需要声明一个数组变量tmp[],大小是n的2倍,这个数组的作用是记录中位数b左侧区间加上右侧区间满足条件的连续子序列
- 从n+1开始遍历,一直遍历到右端点,用sum来记录一个连续子序列的左端点是中位数b的子序列的和, 如果当前sum为0,表示以当前遍历的这个数为右端点,中位数b为左端点的连续子序列的中位数是b
- 每遍历一个点,用tmp[]数组匹配左侧区间
代码实现
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, b;
int ans[N], tmp[N << 1];
int main()
{
scanf("%d%d", &n, &b);
int idx = 0;
for(int i = 1;i <= n; i++)
{
scanf("%d", &ans[i]);
if(ans[i] < b) ans[i] = -1; //小于中位数b的数修改为-1
else if(ans[i] > b) ans[i] = 1; // 大于中位数b的数修改为1
else idx = i; // 找到中位数b在序列中的下标
}
int res = 1; // 记录有多少个连续子序列的中位数是b
int sum = 0; // 记录左侧区间和右侧区间1和-1的总和
for(int i = idx - 1; i >= 1; i--)
{
sum += ans[i];
if(!sum) res++; // 以中位数b为右端点的左侧区间的中位数是b
tmp[n + sum]++; // tmp[]数组,记录左侧区间中大于、小于中位数的种类
}
sum = 0;
for(int i = idx + 1; i <= n; i++)
{
sum += ans[i];
if(!sum) res++; // 以中位数b为左端点的右侧区间的中位数是b
res += tmp[n-sum]; // 左侧区间与右侧区间相匹配的连续子序列
}
printf("%d\n", res);
return 0;
}