洛谷 P1627 [CQOI2009] 中位数

Description

给出 \(1,2,...,n\) 的一个排列,统计该排列有多少个长度为奇数的连续子序列(子段)的中位数是 \(b\)。中位数是指把所有元素从小到大排列后,位于中间的数。

Constraints

对于 \(30\%\) 的数据中,满足 \(n \le 100\)

对于 \(60\%\) 的数据中,满足 \(n \le 1000\)

对于 \(100\%\) 的数据中,满足 \(n \le 100000\) , \(1 \le b \le n\)

Solution

序列长度必为奇数,而排列中只有一个 \(b\),所以中位数为 \(b\) 的子段里肯定包含 \(b\)

又由于中位数只是关心相对位置,而不关心数的具体大小,可以把 \(\lt b\) 的所有数变为 \(-1\),把 \(gt b\) 的所有数变为 \(-1\)\(b\) 变成 \(0\)

问题便转化为在转换后数组中有多少带 \(b\) 且总和为 \(0\) 的子段了。

我们可以考虑从 \(b\) 开始,向数组两边跑前缀/后缀和,然后分类累计答案:

  • 只延伸左边区间,则为左边到 \(b\) 后缀和为 \(0\) 的个数;

  • 只延伸右边区间,则为右边以 \(b\) 开始的前缀和为 \(0\) 的个数;

  • 左右区间均要延伸,设现在找到的左边位置前缀和为 \(i(i≠0)\)\(l[i]\) 个位置, 右边位置前缀和为 \(-i\) 的有 $r[i] 个,则根据组合计算可知,答案应累加 \(l[i] \times r[i]\),对于每一个 \(i\) 加一次即可。

最终总答案便是三类答案之和。

注意一个细节:因为前缀/后缀和可能出现负数,所以前后缀和数组下标要进行处理(如加一个大数)。

Code:

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 5E5 + 5;
const int TEMP = 2E5;
int n, b;
int a[N], f[N];
int l[N], r[N], pos, ans = 1;
int main()
{
	scanf("%d%d", &n, &b);
	rep(i, 1, n)
	{
		scanf("%d", &a[i]);
		if(a[i] > b) a[i] = 1;//转换数组
		else if(a[i] == b) pos = i;
		else a[i] = -1;
	}

	rep(i, pos + 1, n)
	{
		f[i] = f[i - 1] + a[i];//前缀和,由于与后缀和位置不影响,可以合并成一个数组操作
		if(f[i] == 0) ans++;//处理第一种情况
		l[f[i] + TEMP]++;//l数组用来统计和的每个值出现的次数,便于组合计算
	}
	per(i, pos - 1, 1)
	{
		f[i] = f[i + 1] + a[i];//后缀和同理
		if(f[i] == 0) ans++;//处理第二种情况
		r[f[i] + TEMP]++;
	}
	rep(i, TEMP - n, TEMP + n)
	{
		ans += l[i] * r[TEMP - (i - TEMP)];//处理第三种情况
	}
	printf("%d", ans);
	return 0;
}
posted @ 2022-06-21 12:12  panjx  阅读(43)  评论(0编辑  收藏  举报