您的位置:首页 > 其它

【洛谷 P1627】 中位数 递推+前缀和思想

2016-11-14 15:24 288 查看
题目描述

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

输入输出格式

输入格式:

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

【数据规模】

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

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

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

输出格式:

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

输入输出样例

输入样例#1:

7 4

5 7 2 4 3 1 6

输出样例#1:

4

题解:一道思路题。我一开始的思路是利用前缀和,维护两个前缀和数组,maxx[i]和minn[i],分别表示1到i这个点之间一共有多少比b大的数和多少比b小的数。那i~j之间比b大的数就可以用maxx[j]−maxx[i−1]得到。最后枚举区间,复杂度是O(n/4)。这样只能过8个点。

正解:记录下b的位置k,第一次循环从k+1到n循环,到该数大于b时,f[i]=f[i−1]+1,反之f[i]=f[i−1]−1,如果f[i]==0则ans++,然后用一个数组p记录p[f[i]]++,表示记录该数出现了多少次。

然后从k-1到1循环,同样用f[i]记录。然后ans+=p[f[i]],f数组含义就是遇到比b大的数就+1,遇到小的数就-1,那么如果b的左右两边出现同一个f[i],那么说明这两个位置之间比b大的数的个数等于比b小的数的个数,那么直接更新答案。复杂度是O(n).

注意,因为c++数组下标不为负数,所以开数组是把所有下标统一加上100000,保证下标为正,同时注意数组大小也要相应增加100000。

http://blog.csdn.net/mitykif orz

#include<cstdio>
#include<iostream>
#define s 100000
using namespace std;
int a[200006],k,ans=0,n;
int b,minn[200006],maxx[200006],p[200006],f[200006];
int get()
{
int x=0,z=1;
char c;
c=getchar();
if (c==' ') return x*z;
while (c<'0'||c>'9') {if (c=='-') z=-1;c=getchar();}
while (c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
return x*z;
}
void work()
{
int i;
for (i=1;i<=n;i++)
{
if (a[i]>b) {maxx[i]=maxx[i-1]+1;minn[i]=minn[i-1];}
else if (a[i]<b) {minn[i]=minn[i-1]+1;maxx[i]=maxx[i-1];}
else if (a[i]==b) {maxx[i]=maxx[i-1];minn[i]=minn[i-1];continue;}
}
}
void love()
{
int i,j,m1,m2;
for (i=1;i<=k;i++)
for (j=k;j<=n;j++)
if ((j-i)%2==0)
{
m1=maxx[j]-maxx[i-1];
m2=minn[j]-minn[i-1];
if (m1==m2)
{
ans++;
}
}
}
void distance()
{
int i,w=0;
for (i=k+1;i<=n;i++)
{
if (a[i]>b) {f[i]=f[i-1]+1;}
else {f[i]=f[i-1]-1;}
p[f[i]]++;
if (f[i]==s)
ans++;
}
for (i=k-1;i>=1;i--)
{
if (a[i]>b) {f[i]=f[i+1]+1;}
else {f[i]=f[i+1]-1;}
ans+=p[s+s-f[i]];
if (f[i]==s)
ans++;
}
printf("%d",ans+1);
}
int main()
{
int i;
n=get();b=get();
for (i=1;i<=n;i++)
{
a[i]=get();
if (a[i]==b) k=i;
}
f[k]=s;
if (n<=1000)
{
work();
love();
printf("%d",ans);
return 0;
}
distance();

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: