luogu1627 [CQOI2009] 中位数
题目大意
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。
审题
什么叫排列?它是序列1,2,3,...,n打乱顺序后的序列。所以b只出现一次。
思路
根据中位数的定义,我们必须以把b放到一个序列中,使该序列单调递增作为入手点吗?不。因为中位数是固定的,所以只要得到的这个序列比b大的数和比b小的数相等即可。如何快速处理这一点呢?把比b大的数设为1,比b小的数设为-1,则一段连续的序列如果所有元素之和为0,则该序列的中位数是b。(补充:人家要的是元素个数为奇数个的序列,如果序列中的元素为奇数,只有1,-1,则该序列之和不可能为0.所以序列必然含有b。)为了得到所有含有b的序列的元素之和,我们要计算b所在位置左序列的后缀和、右序列的前缀和,这样区间[位于左序列的位置i,位于右序列的位置j]的元素和就可以知道了。然而为了枚举[i,j]内元素和为0的i,j个数,直接枚举\(O(n^2)\),不行。所以我们可以对左区间后缀和的值和右区间前缀和的值各维护一组桶表示值出现的次数。这样我们所要求的便是-N~N中 所有值i在左i桶和右-i桶中存放的个数的乘积 之和。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 100010, MAX_RANGE_SIZE = MAX_N * 2;
int A[MAX_N], lSuffix[MAX_N], rPrefix[MAX_N];
int L[MAX_RANGE_SIZE], R[MAX_RANGE_SIZE];
int N, B, Pos;
#define lSumValCnt(i) L[i + 100000]
#define rSumValCnt(i) R[i + 100000]
void Read()
{
int x;
scanf("%d%d", &N, &B);
for (int i = 1; i <= N; i++)
{
scanf("%d", &x);
if (x == B)
{
A[i] = 0;
Pos = i;
}
else if (x < B)
A[i] = -1;
else if (x > B)
A[i] = 1;
}
}
void GetLsuffix()
{
lSuffix[Pos] = 0;
for (int i = Pos - 1; i >= 1; i--)
lSuffix[i] = lSuffix[i + 1] + A[i];
}
void GetRprefix()
{
rPrefix[Pos] = 0;
for (int i = Pos + 1; i <= N; i++)
rPrefix[i] = rPrefix[i - 1] + A[i];
}
void GetLsumValCnt()
{
for (int i = Pos; i >= 1; i--)
lSumValCnt(lSuffix[i])++;
}
void GetRsumValCnt()
{
for (int i = Pos; i <= N; i++)
rSumValCnt(rPrefix[i])++;
}
int GetAns()
{
int ans = 0;
for (int i = N; i >= -N; i--)
ans += lSumValCnt(i)*rSumValCnt(-i);
return ans;
}
int main()
{
Read();
GetLsuffix();
GetRprefix();
GetLsumValCnt();
GetRsumValCnt();
printf("%d\n", GetAns());
return 0;
}