🎈描述
原题链接:https://www.acwing.com/problem/content/4668/
小蓝老师教的编程课有 N 名学生, 编号依次是 1…N 。第 i号学生这学期 刷题的数量是 A i A_{i} Ai 。
对于每一名学生, 请你计算他至少还要再刷多少道题, 才能使得全班刷题 比他多的学生数不超过刷题比他少的学生数。
输入格式
第一行包含一个正整数 N。
第二行包含 N 个整数: A 1 , A 2 , A 3 … , A N A_{1}, A_{2}, A_{3} \ldots, A_{N} A1,A2,A3…,AN,
数据范围
对于 100% 的数据,
1
≤
N
≤
100000
,
0
≤
A
i
≤
100000
1≤N≤100000,0≤A_i≤100000
1≤N≤100000,0≤Ai≤100000。
输出格式
输出 N个整数, 依次表示第1…N 号学生分别至少还要再刷多少道题。
输入样例:
5
12 10 15 20 6
输出样例:
0 3 0 0 7
💡思路
-
假设某位同学的刷题数是A,根据题目,需要知道刷题比A多的人数 以及 刷题比A少的人数
- 即查询某一刷题数区间内的 人数
- 即用到前缀和数组,设前缀和数组是pre
- 可知, pre[A]代表 刷题数在[1,A] 范围的人数
- 刷题比A多的人数表示为: pre[100000]-pre[A]
- 即刷题数在[A+1,100000] 区间的人数,(根据数据范围A最大是100000)
- 刷题比A少的人数表示为: pre[A-1]
- 即刷题数在[1,A-1] 区间的人数
- 注意A=0时,显然数组越界,A=0即刷题比0少的人数,为0,写代码时处理一下即可
-
刷题比
A
多的人数
不超过
刷题比
A
少的人数
\color{red}{刷题比A多的人数}\color{blue}{不超过}\color{green}{刷题比A少的人数}
刷题比A多的人数不超过刷题比A少的人数表示为:
- p r e [ 100000 ] − p r e [ A ] < = p r e [ A − 1 ] \color{red}{pre[100000]-pre[A]} \color{blue}{ <=}\color{green}{pre[A-1]} pre[100000]−pre[A]<=pre[A−1]
-
遍历a数组
- 如果
A
i
A_i
Ai已经满足条件,
p
r
e
[
100000
]
−
p
r
e
[
A
i
]
<
=
p
r
e
[
A
i
−
1
]
pre[100000]-pre[A_i]<=pre[A_i-1]
pre[100000]−pre[Ai]<=pre[Ai−1]
- 直接输出0即可
- 否则
- 希望找到一个满足条件的 目标刷题数 T
-
A
i
A_i
Ai刷不够T这个数,不满足条件; 刷到T了,恰好满足条件;继续刷更满足条件
- 因为刷的题目越多 刷题比我多的人越会变少,比我少的人越会变多
- 即 这个T 要尽量小,因为求的是最少刷题数
- 因此,T就满足二段性,可以二分找到T
- 那么 A i A_i Ai要达到T才满足条件 即 A i A_i Ai至少再刷T- A i A_i Ai道题
- 要特别注意一个细节
- 从A刷到T: A …T
- 那么站在T的视角看看T是否满足条件,比T少的人数 要减1
- 因为原先有一个A,刷A题的人数比T的人数少,这个A已经刷到T了,即刷A题的人数少了1,所以比T少的人数 要减1
-
A
i
A_i
Ai刷不够T这个数,不满足条件; 刷到T了,恰好满足条件;继续刷更满足条件
- 希望找到一个满足条件的 目标刷题数 T
- 如果
A
i
A_i
Ai已经满足条件,
p
r
e
[
100000
]
−
p
r
e
[
A
i
]
<
=
p
r
e
[
A
i
−
1
]
pre[100000]-pre[A_i]<=pre[A_i-1]
pre[100000]−pre[Ai]<=pre[Ai−1]
-
时间复杂度O(nlogn)
📝代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
static final int N=100000;
static int []a=new int[N+5];
static int []pre=new int[N+5];
static int n;
public static void main(String[] args) throws Exception {
n=Integer.parseInt(br.readLine());
String []s=br.readLine().split(" ");
for(int i=1;i<=n;i++) {
a[i]=Integer.parseInt(s[i-1]);
pre[a[i]]++;
}
//求前缀和
for(int i=1;i<=100000;i++)
pre[i]+=pre[i-1];
for(int i=1;i<=n;i++) {
//满足条件直接输出0
if(pre[100000]-pre[a[i]]<=pre[Math.max(a[i]-1, 0)]) {
System.out.print(0);
if(i<n)System.out.print(" ");
continue;
}
//二分的边界,a[i]不满足条件,a[i]+1可能满足条件,因此左边界是a[i]+1
int l=a[i]+1;
int r=N;
while(l<r) {
int m=(l+r)>>1;
if(pre[100000]-pre[m]<=pre[Math.max(m-1, 0)]-1) //注意-1这个细节
r=m;
else
l=m+1;
}
System.out.print(l-a[i]);
if(i<n) System.out.print(" ");
}
}
}