[bzoj]1303: [CQOI2009]中位数图

原题链接1303: [CQOI2009]中位数图

首先审题一定要清晰!!

一开始看错题目认为是一个任意的序列,然后枚举d的位置绕了很大的弯子。

看到题目可以以为是排序,这也有了第一个方法。

一、朴素枚举

枚举每一个包含d的序列,对序列进行排序,二分查找d的位置,若在序列中间,则累加到ans变量里面。明显这种算法很naive。

二、前缀和数组

认真审题以后发现,每个数无非三种可能:比d大,比d小,等于d,若一个序列的中位数为d,那么比d大的数一定和比d小的数个数相同。

可以标记比d大的数为1,比d小的数位-1。这样子就可以把问题转化为统计前缀和了。

一开始想到用树状数组。后来想起不需要支持修改操作,只需要在第一遍读入的时候进行统计前缀和即可。

 2.1 TLE操作O(n^2)

然后的思路是以d为中点,往两边拓展区间,由于题目中要求区间为奇数序列,那么往左边拓展奇数个时,右边也要拓展奇数个,枚举区间判断左边的和+右边的和是否为0。

for(int i=num;i>0;i--)
    for(int j=((num-i+1)&1)?num:num+1;j<=n;j+=2)
        if(sum[j]-sum[i-1]==0)
           ans++;

好吧枚举区间是真的慢。

2.2预处理优化时间O(n)

注意到左边的数等于右边的数,因为左边的数一定,只需要统计右边有多少个区间的数和左边的区间是互为相反数的即可。

预处理出右区间每一个数值的个数(桶排!),每次得到左区间的数值,只需要在O(1)的时间里查询右区间的个数即可。

注意坑:若左区间的数值为0,那么不需要右区间也可以组成一个中位数区间,此时要直接统计进答案。同理右区间和中位数的单区间也一样。

    for(int i=num+1;i<=n;i+=2){
        r1[a[i]-a[num]+K]++;
        if(a[i]-a[num]==0)ans++;
    }
    for(int i=num+2;i<=n;i+=2){
        r2[a[i]-a[num]+K]++;
        if(a[i]-a[num]==0)ans++;
    }
    //for(int i=num-1;i>0;i--)l[a[num-1]-a[i]+K]++;
    //cout<<r[K-1]<<endl;
    for(int i=1;i<num;i++){
        if(a[num-1]-a[i-1]==0)ans++;
        if((num-i+1)&1)
            ans+=r2[K-(a[num-1]-a[i-1])];
        else ans+=r1[K-(a[num-1]-a[i-1])];
    }
    cout<<(ans+1)<<endl;]++;   

最后一点小优化:不需要分成两个数组储存,由于除了中间的数外只有1和-1,如果左区间长度为奇数,那么它的值也是奇数,右区间只有奇数长度的能和它对应,那么就不需要考虑奇偶数分开存放了。

#include <iostream>
#include <cstdio>
using namespace std;
const int K=99999/2;
int n,b,num=0,ans=0;
int a[100009],r1[100009],r2[100009];
void read(int k){
    int tmp=0;
    char ch=getchar();
    while(ch>'9'||ch<'0')ch=getchar();
    while(ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0';ch=getchar();}
    if(tmp>b)a[k]=a[k-1]+1;
    else if(tmp<b)a[k]=a[k-1]-1;
    else a[k]=a[k-1];
    if(tmp==b)num=k;
    return;
}
int main()
{
    scanf("%d%d",&n,&b);
    for(int i=1;i<=n;i++)read(i);
    for(int i=num+1;i<=n;i+=2){
        r1[a[i]-a[num]+K]++;
        if(a[i]-a[num]==0)ans++;
    }
    for(int i=num+2;i<=n;i+=2){
        r2[a[i]-a[num]+K]++;
        if(a[i]-a[num]==0)ans++;
    }
    //for(int i=num-1;i>0;i--)l[a[num-1]-a[i]+K]++;
    //cout<<r[K-1]<<endl;
    for(int i=1;i<num;i++){
        if(a[num-1]-a[i-1]==0)ans++;
        if((num-i+1)&1)
            ans+=r2[K-(a[num-1]-a[i-1])];
        else ans+=r1[K-(a[num-1]-a[i-1])];
    }
    cout<<(ans+1)<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值