题意:给你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;
}