luogu P1627中位数解题报告

题目描述

给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。
输入格式

第一行为两个正整数n和b,第二行为1~n的排列。

数据规模

对于30%的数据中,满足n≤100;

对于60%的数据中,满足n≤1000;

对于100%的数据中,满足n≤100000,1≤b≤n。

输出格式

输出一个整数,即中位数为b的连续子序列个数。

输入输出样例

输入:
7 4
5 7 2 4 3 1 6
输出:
4

题解

首先 数列中必须存在数b
对于满足条件的数列,分三种情况讨论
1.右端点为b
2.左端点为b
3.左右端点都不为b
不用怎么仔细研究就可以发现满足条件的数列必须满足整个数列中大于b的数的数量和小于b的数量相等
所以整个数列中的数跟它本身大小没有什么联系
所以将原数列表示成一个只有-1,1,0的新数列,其中小于b的数表示为-1,大于b的数表示为1,b就变0就行了(不变也没事)
这样有啥用呐 只要数列左边的数之和和右边的数之和的和为0,就满足条件惹
然后再以b为分界线 前后分别记一个前缀和(后缀和)
以样例为例
5 7 2 4 3 1 6 -> 1 1 -1 0 -1 -1 1 (第一次变换) -> 1 0 -1 0 -1 -2 -1 (所记录的前缀和(后缀和))
这个前缀和(后缀和)直接表示的就是这个位置到b这个位置里大于0的数和小于0的数的差,当这个和的值为0时,表示从b到该位置的这一段里满足条件,直接统计
所以情况1和2直接就在前缀和后缀和的预处理里面统计完了
再仔细想想这个前缀和(后缀和)的性质,我们发现啊,当0之前的某个数和0之后的某个数之和为0的时候,满足条件,换个角度想,把0之前的数全都取个相反数
那么只要0之前的某个数和0之后相等,就满足条件了
所以开两个桶来保存前缀和和后缀和的值的个数,注意此时的后缀和或者前缀和要有一个取反,如果担心数组越界就加个常数(本题数据很水直接+10000)
最后枚举1~20000所有的数,用乘法原理统计情况3的答案

代码

#include <cstdio>
#define ll long long
#define R register
using namespace std;
int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') { if (c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
	return x * f;
}
const int maxn=1e5+5;
int n,b,zj,ans;
int a[maxn],q[maxn],s[maxn],w[20000],W[20000];
int min(int a,int b){return a<b?b:a;}
void init(){
	n=read(),b=read();
	for (R int i(1);i<=n;++i) {
		a[i]=read();
		if (a[i]==b) zj=i;
		else if (a[i]<b) q[i]=1;
		else q[i]=-1;
	}
}
void doit(){
	for (R int i(zj-1);i>=1;--i) {
		s[i]=s[i+1]+q[i];
		++w[-s[i]+10000];
		if (s[i]==0) ++ans;
	}
	for (R int i(zj+1);i<=n;++i) {
		s[i]=s[i-1]+q[i];
		++W[s[i]+10000];
		if (s[i]==0) ++ans;
	}
	for (int i(1);i<=20000;++i) {
		ans+=w[i]*W[i];
	}
	printf("%d
",ans+1);
}

signed main(){
	init();
	doit();
	return 0; 
}
原文地址:https://www.cnblogs.com/cancers/p/11316029.html