BZOJ1303 || 洛谷P1627 [CQOI2009]中位数【巧妙的思路】

Time Limit: 1 Sec
Memory Limit: 162 MB

Description

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

Input

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

Output

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

HINT

N<=100000


题目分析

数据结构学傻了,一开始绞尽脑汁想数据结构
看了题解真的不得不赞叹思路真的十分巧妙

我们把序列中小于b的数改成-1,大于的改成1
这样如果某个子序列和为0,那么这个子序列中位数就是b

假设排列中b的位置为p
l s u m [ i ] , r s u m [ i ] lsum[i],rsum[i] lsum[i],rsum[i]分别表示p左边 i i i ~ p p p的和p右边 p p p ~ i i i的和
c n t l [ i ] cntl[i] cntl[i]分别表示 p p p p p p左边和为 i i i的数量 c n t r [ i ] cntr[i] cntr[i]表示右边
从p分别向前向后扫描即可得到这四个数组

最后根据乘法原理 a n s = ∑ i = − n + 1 n − 1 c n t l [ i ] ∗ c n t r [ 0 − i ] ans=\sum_{i=-n+1}^{n-1}cntl[i]*cntr[0-i] ans=i=n+1n1cntl[i]cntr[0i]
注意为了处理数组下表出现负数的问题
可以在处理的时候令每个值+n


#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
  
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}
  
const int maxn=500010;
int n,b;
int a[maxn],p;
int lsum[maxn],rsum[maxn];
int cntl[maxn],cntr[maxn];
lt ans;
  
int main()
{
    n=read();b=read();
    for(int i=1;i<=n;++i) 
    {
        a[i]=read();
        if(a[i]==b) a[i]=0,p=i;
        else a[i]=(a[i]>b)?1:-1;
    }
    cntl[n]=cntr[n]=1;//注意包括p位置的0,所以cntl[0+n]=cntr[0+n]=1
    for(int i=p-1;i>=1;--i)
    {
        lsum[i]=lsum[i+1]+a[i];
        ++cntl[lsum[i]+n];//防止负数下表所以值要+n
    } 
    for(int i=p+1;i<=n;++i)
    {
        rsum[i]=rsum[i-1]+a[i];
        ++cntr[rsum[i]+n];
    } 
    for(int i=-n+1;i<n;++i)
    ans+=cntl[i+n]*cntr[0-i+n];
    printf("%lld",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值