The Preliminary Contest for ICPC Asia Xuzhou 2019 - E XKC's basketball team | 思维 | 二分

题意:给你n个数字,寻找第i个数字后面比i大至少m且距离i最远的数字。

理解起来不难,就是怎么能高效率地解决问题。我开始想用struct按大小排序,之后对每个i往前搜,不断更新MAX_DISTANCE。但是这样对于每个数字N都重复,复杂度是O(n2),会T。想了半天没想出优化的方法。还是队友tql,xs提供了一个思路,从后往前扫,用一个数组盛放最大值(再搞一个同时记录坐标),不断更新最大值并放入。这样这个最大值数组一定是递增,不需要额外sort增加开支就可以直接二分。

也就是说,从后往前对于每一个数字,如果数字Wi更新了最大值,那毫无疑问后面不会有比它大m的数字了,anger[i] = -1。而如果数字没有更新最大值,那么就要在之前存储的最大值数列中进行查找。因为我们的最大值数组,它不仅是递增,还满足一个条件,那就是从maxn[0]到maxn[cnt]他们对应的下标是逐次减少的。那么我们二分的时候只要找到最接近Wi+m且大于的,它一定是最远下标。

这么写了之后一直在WA,卡了很久,xs觉得可能是我最开始手写的二分有问题,就换成lower_bound,但是还是WA。其实这里还有一种情况,那就是,虽然这个数字没有更新最大值,但是不意味着我们在maxn数组中一定能找到符合它条件的那个值。因为我们找的不是比Wi大且最远,而是比Wi+m大且最远。试想假如n = 3, m = 7,那么对于5 6 10,使用lower_bound的时候就是找不到答案的。

搜不到的时候lower_bound会返回一个指向末端(.end())的迭代器,所以如果不区分的话肯定会就WA。我们只要判断一下这个返回值是不是数组最后一个元素的地址即可。

#include <bits/stdc++.h>

using namespace std;

#define MAX_N 500010
#define ll long long

ll n, m, cnt;
ll w[MAX_N], anger[MAX_N], maxn[MAX_N], xiabiao[MAX_N];

int main()
{
    scanf("%lld %lld", &n, &m);
    for(int i = 1;i <= n;i++)
        scanf("%lld", &w[i]);

    ll truemax = -999;
    cnt = 0;
    for(int i = n;i >= 1;i--)
    {
        //printf("w[%d] = %d\n", i, w[i]);
        //从后向前
        if(w[i] > truemax)//最大值
        {

            truemax = w[i];
            maxn[cnt] = w[i];
            xiabiao[cnt] = i;
            //printf("i = %d  cnt = %d  maxn[cnt] = %d  xiabiao[cnt] = %d\n", i, cnt, maxn[cnt], xiabiao[cnt]);
            anger[i] = -1;
            cnt++;
            //printf("anger[%d] = %d\n", i, anger[i]);
        }

        else
        {
            //printf("w[%i] = %d   w[%d]+m = %d\n", i, w[i], i, w[i]+m);
            ll *ans = lower_bound(maxn, maxn+cnt, w[i]+m);
            if(ans==maxn+cnt)
                anger[i]=-1;
            else//最接近的最大值的下标
            //printf("i = %d ans = %d\n", i, ans);
            anger[i] = xiabiao[ans-maxn] - i - 1;
            //printf("i = %d  xiabiao[ans] = %d anger[i] = %d\n", i, xiabiao[ans], anger[i]);
        }
    }

    for(int i = 1;i <= n;i++)
    {
        if(i == n)
            printf("%lld", anger[i]);

        else
            printf("%lld ", anger[i]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值