题目描述

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

输入描述:

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

输出描述:

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

输入

7 4
5 7 2 4 3 1 6

输出

4

思路1:(大佬直接跳到思路4即可)纯暴力,对于中位数,我们肯定知道对于一个序列(排好序后),以中位数x为分界线,左边比x小的数和右边比x大的数个数相同,直接的想法就是暴力枚举每一种可能的区间,判断b是否在区间以及是否满足排好序后(暴力的话大可不必排序这个区间,只需枚举区间中比b大的个数和比b小的个数即可)小的和大的一样多,这种方法时间复杂度O(n^3)


#include<iostream>
using namespace std;
const int N=1e5+5;
int a[N],ans;
bool judge(int i,int j,int t)
{
    int minx=0,max=0;
    for(int k=i;k<=j;k++)
    {
        if(a[k]>a[t])
        max++;
        else if(a[k]<a[t])
        minx++;
    }
    return minx==max;
}
int main()
{
    int n,x,t;
    scanf("%d%d",&n,&x);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i]==x)t=i;
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        if(i<=t&&t<=j&&judge(i,j,t))
        ans++;
    }
    printf("%d\n",ans);
    return 0;
}
//通过50%

思路2:优美暴力

我们在此方法上对O(n^3)进行优化,我们是不是可以对这个排列预处理,也就是求前缀和,对于minx[i]表示前i个数中比b小的个数,maxx[i]表示前i个数中比b大的个数,那么我们只用两层循环就可以了O(n^2)。


#include<iostream>
using namespace std;
const int N=1e5+5;
int a[N],ans,minx[N],maxx[N];
int main()
{
    int n,x,t;
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i]==x)t=i;
    }
    for(int i=1;i<=n;i++)//预处理过程基本上就是核心了
    {
        if(a[i]<a[t])minx[i]++;
        else if(a[i]>a[t])maxx[i]++;

        minx[i]+=minx[i-1];
        maxx[i]+=maxx[i-1];

    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        if(i<=t&&j>=t&&(minx[j]-minx[i-1])==(maxx[j]-maxx[i-1]))
        {
            ans++;
            //printf("%d %d\n",i,j);
        }
    }
//    for(int i=1;i<=n;i++)
//    printf("%d %d\n",minx[i],maxx[i]);
    printf("%d\n",ans);
    return 0;
}
//ac80%

思路3:也就是最优解,对于某个区间{L,R}
中位数b:左边
--L小:表示在b左边比b小的个数
--L大:表示在b左边比b大的个数
中位数b:右边
--R小:表示在b右边比b小的个数
--R大:表示在b右边比b大的个数
上述条件满足:L小+R小=L大+R大,变换一下:L小-L大=R大-R小=j
只要存在L小-L大=R大-R小 就说明这段区间{L,R}满足条件,接下来我们枚举从b开始到n的区间,求前缀和,maxx[i]和minx[i],并且统计cnt[maxx[i]-minx[i]=j]++,也就是这个j的数量,这里用map统计即可(因为考虑到可能存在负值情况,或者在统计值j的时候+n防止越界),上述过程就是在从b右边开始统计j的数量,那么我们是不是再从b左边开始求minx[i]-maxx[i]=j的值(注意这里和变换那里条件要一样),统计这个j值的数量是不是就是对答案的贡献,当然在枚举做区间时,要记得res=cnt[0],这种情况要先被加进去(因为=0的情况已经符合条件了,不懂得模拟一下即可)。最后输出res即可

#include<iostream>
#include<map>
using namespace std;
const int N=1e5+5;
int a[N],ans,minx[N],maxx[N];
map<int,int>m;
int main()
{
    int n,x,t;
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i]==x)t=i;//找到这个位置
    }
    for(int i=t;i<=n;i++)//右端点前缀和处理
    {
        if(a[i]<a[t])minx[i]++;
        else if(a[i]>a[t])maxx[i]++;
        minx[i]+=minx[i-1];
        maxx[i]+=maxx[i-1];
        m[maxx[i]-minx[i]]++;//这里就是我们要找的R大-R小得个数key(maxx[i]-minx[i])代表上述思路的j
    }
    ans=m[0];//一开始在右端点就已经有满足的了,因为j=0,就代表大于和小于的个数相等
    for(int i=t-1;i>=1;i--)//枚举左端点,这里一定是从左端点末尾开始,因为要用前缀和处理,从小到大开始处理不了i到x的大于和小于个数
    {
        if(a[i]<a[t])minx[i]++;
        else if(a[i]>a[t])maxx[i]++;
        minx[i]+=minx[i+1];
        maxx[i]+=maxx[i+1];
        ans+=m[minx[i]-maxx[i]];//这里就是我们要找的L小-L大=j看有多少个和R大-R小=j相等而通过key就直接能找到相等的个数
    }
    printf("%d\n",ans);
    return 0;
}
//AC

思路4:对思路3的优化,我们直接让这个区间中比b大的=1,比b小的=-1,那么直接用sum对右区间求前缀和,同时统计值的数量(这里看到其他的题解都是在统计的前把cnt[N]=1,然后又在统计过程中把cnt[0]贡献到答案中,而我是从b的位置开始统计相当于cnt[N]=1,然后统计完值,将res=cnt[N]了,根据个人喜好把,理解即可),然后对左区间进行求后缀和,同时贡献答案


#include<iostream>

using namespace std;
const int N=1e5+5;
int a[N],cnt[N<<1];//N<<1防止N过于大造成段错误(因为自己发生数组越界了QAQ)
int main()
{
    int n,b,pos;
    cin>>n>>b;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(a[i]>b)a[i]=1;
        else if(a[i]<b)a[i]=-1;
        else if(a[i]==b)a[i]=0,pos=i;
    }
    int sum=0;
    for(int i=pos;i<=n;i++)
    {
        sum+=a[i];
        cnt[sum+n]++;
    }
    int res=0;
    res=cnt[n],sum=0;
    for(int i=pos-1;i>=1;i--)
    {
        sum+=a[i];
        res+=cnt[n-sum];
    }
    cout<<res<<endl;
    return 0;
}

//ac

总结:思路4大部分就是题解区的操作了,细节部分只需要注意一下,本身中位数b(也就是cnt[0+n])就是对答案的一种贡献,题解区都是将cnt[n]=1,直接这样统计到答案里边,这里我是把a[i]==b时,a[i]=0,然后直接从pos位置枚举,也把它统计到答案里边了,另外在有区间还有cnt[0+n]的情况也需要统计到答案里边,最后只需res=cnt[n]即可,然后去枚举左区间就行了。(写这篇题解是因为在之前遇到过这道题,然后在这里碰到了,就是换了种说法,又发现不会了...)

换种说法的传送门:传送门