HDU 5701 中位数计数 (思维+枚举技巧)

42 篇文章 0 订阅
37 篇文章 0 订阅
本文探讨了一种寻找中位数的方法,强调了不能通过双层循环枚举所有区间来找到中位数,因为这会导致超时。正确思路是利用中位数的性质,即比它大的数等于比它小的数。通过记录区间内数字的相对大小,以当前数为中心分别向左和向右枚举,找到符合条件的区间。当左右两边区间大小相等时,即可组合成满足中位数条件的区间。这种思维和枚举技巧在解决此类问题时十分关键。
摘要由CSDN通过智能技术生成



中位数计数

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1401    Accepted Submission(s): 521


Problem Description
中位数定义为所有值从小到大排序后排在正中间的那个数,如果值有偶数个,通常取最中间的两个数值的平均数作为中位数。

现在有 n 个数,每个数都是独一无二的,求出每个数在多少个包含其的区间中是中位数。
 

Input
多组测试数据

第一行一个数 n(n8000)

第二行 n 个数, 0≤       每个数 109 ,
 

Output
N 个数,依次表示第 i 个数在多少包含其的区间中是中位数。
 

Sample Input
  
  
5 1 2 3 4 5
 

Sample Output
  
  
1 2 3 2 1
 

Source

为什么看了题解之后感觉明明可以想到的啊,可是自己想了好久还是做不出来,方法不对啊。。。大哭,看到这个题没有直接往这里想。。。希望以后可以再成长一些吧

思路:肯定不可以两个for枚举区间在每个区间里找到中间大的数字,绝壁超时 ,既然是中位数,也就是这个数比他大的数 == 比他小的数,然而包含一个数的区间怎么找呢,无非就是这个数为右边界,为左边界,在中间。用一个cnt记录这个区间里总的大小,比他大就++,比他小就--,如果==0说明这个就是中位数(明明用过好多次为什么就是想不到哭),先以这个点往左枚举, == 0,说明在这个区间是中位数,再以这个点往右枚举, == 0说明是中位数,但是注意之所以要先这样枚举,是找可以直接找到这个数在区间中间的个数,只要左面+右面的cnt == 0,说明这两个区间拼在一起可以符合要求,(这里的cnt是“总的”的值,>他的 < 去小于他的)。 然后就是组合左面跟右面的区间,只要他们的值相同,就可以组合在一起。。

一般的写法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 8e3;
int a[maxn], cnt1[maxn<<1], cnt2[maxn<<1];
int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= n; i++)
        {
            memset(cnt1, 0, sizeof(cnt1));
            memset(cnt2, 0, sizeof(cnt2));
            int ans = 1, cnt = 0;
            for(int j = i - 1; j >= 1; j--)
            {
                cnt += a[i] > a[j] ? 1 : -1;
                if(!cnt) ans++;  //这里的ans++指的是不用左右区间组合的情况下可以是中位数的
                cnt1[n+cnt]++;  //这里记录 总的“比他小的” 用比他小大-比他大的
            }
            cnt = 0;   
            for(int j = i + 1; j <= n; j++)
            {
                cnt += a[i] < a[j] ? 1 : -1; //这里记录的总的“比他大的”,只要左右比他大==比他小就可以组合在一起
                if(!cnt) ans++;  //这里是单一右面区间可以为中位数的值
                cnt2[n+cnt]++;
            }
            for(int i = 0; i <= n*2; i++)  //这里就是左右区间组合,数值多大,说明有几种不同的长度的区间,组合方式肯定是左面*右面的;
                ans += cnt1[i]*cnt2[i];
            printf("%d%c", ans, i == n ? '\n' : ' ');
        }
    }
    return 0;
}

网上大牛的写法,写的真是妙,时间也快了一倍:

#include <cstdio>
#include <cstring>
int const MAX = 8005;
int const CON = 8000;
int a[MAX], has[MAX << 1];

int main()
{
    int n;
    while(scanf("%d", &n) != EOF)
    {
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= n; i++)
        {
            int cnt = 0, ans = 0;
            memset(has, 0, sizeof(has));
            has[CON] ++;
            for(int j = i + 1; j <= n; j++)
            {
                cnt += a[j] > a[i] ? 1 : -1;
                has[CON + cnt] ++;
            }
            cnt = 0;
            ans += has[CON]; //这里就是单一区间是0的;
            for(int j = i - 1; j >= 1; j--)
            {
                cnt += a[j] < a[i] ? 1 : -1;
                ans += has[CON + cnt];  //遇到一个相同的,就++右面区间的数值,先直接枚举一个区间,然后中间的跟左面都就求出来了、、
            }
            printf("%d%c", ans, i == n? '\n' : ' ');
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值