为了更好的阅读体检,可以查看我的算法学习网
在线评测链接:P1471
题目内容
塔子哥有一个长度为 n n n 的数组 a a a ,但是塔子哥很喜欢众数,所以他想改造这个数组,使得众数的出现次数尽可能多。
一次操作,塔子哥可以选择两个下标 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i, j (1\leq i,j\leq n, i\neq j) i,j(1≤i,j≤n,i=j),使得 a i a_i ai 减 1 1 1 , a j a_j aj 加 1 1 1 。
现在塔子哥想问你,使得众数出现次数最多的情况下,最少的操作次数是多少。
输入描述
第一行,一个正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n\leq 10^5) n(1≤n≤105) ,表示数组 a a a 的大小
第二行, n n n 个正整数 a i ( 1 ≤ a i ≤ 1 0 9 ) a_i(1\leq a_i\leq 10^9) ai(1≤ai≤109)
输出描述
使得众数出现次数最多的情况下的最少操作次数。
样例
输入
3
1 2 3
输出
1
说明
a
1
a_1
a1 加
1
1
1 ,
a
3
a_3
a3 减
1
1
1 ,一次操作得到 [2, 2, 2]
思路:思维
记录 s u m = ∑ i = 0 n − 1 a i sum = \sum\limits_{i=0}^{n-1} a_i sum=i=0∑n−1ai
如果 s u m % n = 0 sum \% n =0 sum%n=0 ,则所有数都可以修改为 s u m n \frac{sum}{n} nsum 。
否则,必然可以将
n
−
1
n-1
n−1 个数通过操作修改为同样的数。
这是因为,假设使得
a
[
1
]
,
a
[
2
]
⋯
,
a
[
n
−
2
]
,
a
[
n
−
1
]
a[1],a[2] \cdots,a[n-2], a[n - 1]
a[1],a[2]⋯,a[n−2],a[n−1] 都变为
1
1
1 ,则
a
[
i
]
a[i]
a[i] 变为
1
1
1 的增加或者减少,都由
a
[
0
]
a[0]
a[0] 来配对 。
现在问题转换为:
- 将哪个数作为配对数
- 将 n − 1 n-1 n−1 个数都转换为哪些数
对于问题 1 :每个数都有可能,枚举每个数作为配对数
对于问题 2 :转换为平均数,这样我们就可以尽可能使得 配对数使用的次数尽可能少
为什么转换为平均数就是最少的?
首先,确定最终每个数都转换为 target
那么总的操作为:
有
l
t
lt
lt 个小于
t
a
r
g
e
t
target
target 的数,这些数的和为
l
t
_
s
u
m
lt\_sum
lt_sum ,则
x
=
l
t
×
t
a
r
g
e
t
−
l
t
_
s
u
m
x = lt \times target - lt\_sum
x=lt×target−lt_sum 是需要加
1
1
1 的部分
有
g
t
gt
gt 个大于
t
a
r
g
e
t
target
target 的数,这些数的和为
g
t
_
s
u
m
gt\_sum
gt_sum ,则
y
=
g
t
_
s
u
m
−
g
t
×
t
a
r
g
e
t
y = gt\_sum - gt \times target
y=gt_sum−gt×target 是需要减
1
1
1 的部分
最后的总操作数为 m a x ( x , y ) max(x, y) max(x,y)
所以我们要保证这两者尽可能接近,这样 m a x ( x , y ) max(x, y) max(x,y) 才能尽可能小,这时候 t a r g e t target target 就得是这 n − 1 n-1 n−1 个数的平均数 a v e r a g e average average 了 。
假设 x _ a v e x\_ave x_ave 为 小于 t a r g e t target target 的数增加到 a v e r a g e average average 的部分, y _ a v e y\_ave y_ave 为 大于 t a r g e t target target 的数减少到 a v e r a g e average average 的部分,此时 x _ a v e = y _ a v e x\_ave = y\_ave x_ave=y_ave。
如果我们调整 t a r g e t < a v e r a g e target < average target<average ,则大于 t a r g e t target target 的部分必然需要大于 y _ a v e y\_ave y_ave 次减 1 1 1 才能到达 t a r g e t target target
如果我们调整 t a r g e t > a v e r a g e target > average target>average ,则小于 t a r g e t target target 的部分必然需要大于 x _ a v e x\_ave x_ave 次加 1 1 1 才能到达 t a r g e t target target
所以答案 t a r g e t target target 就是 a v e r a g e average average 。
注意:当 a v e r a g e average average 不是整数时,有两个可能,一个是 f l o o r ( a v e r a g e ) floor(average) floor(average) ,一个是 c e i l ( a v e r a g e ) ceil(average) ceil(average) ,这两者都考虑一下即可 。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
代码
C++
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
ll sum = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
sum += a[i];
}
// sum % n == 0,则 a[i] 都可以改为 ave = sum / n
if (sum % n == 0) {
ll ans = 0;
ll ave = sum / n;
for (int i = 0; i < n; ++i) {
ans += abs(a[i] - ave);
}
cout << ans / 2 << "\n";
return 0;
}
sort(a.begin(), a.end());
vector<ll> pre(n + 1);
for (int i = 0; i < n; ++i) {
pre[i + 1] = pre[i] + a[i];
}
auto get_operation = [&](ll ave, ll cur_sum, int idx) {
// 小于 ave 的部分,所以需要二分找到小于 ave 的最大的数
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (a[mid] < ave) l = mid;
else r = mid - 1;
}
// 计算小于 ave 的数的和 lt_sum ,小于 ave 的数的个数 lt_cnt
ll lt_sum = 0;
int lt_cnt = 0;
// 计算大于等于 ave 的数的和 gt_sum ,大于等于 ave 的数的个数 gt_cnt
ll gt_sum = 0;
int gt_cnt = 0;
// 说明 a[idx] >= ave ,属于大于等于 ave 的部分
if (idx > l) {
lt_sum = pre[l + 1];
lt_cnt = l + 1;
gt_sum = cur_sum - lt_sum;
gt_cnt = n - 1 - lt_cnt;
} else {
// 说明 a[idx] < ave ,属于小于 ave 的部分
gt_sum = pre[n] - pre[l + 1];
gt_cnt = n - (l + 1);
lt_sum = cur_sum - gt_sum;
lt_cnt = n - 1 - gt_cnt;
}
// 小于 ave 的数需要增加
ll x = ave * lt_cnt - lt_sum;
// 大于等于 ave 的数需要减小
ll y = gt_sum - ave * gt_cnt;
// 取两者较大值
return max(x, y);
};
auto get_ans = [&](int idx) {
// n-1 个数的总和
ll cur_sum = sum - a[idx];
// 计算 n-1 个数的平均值
ll ave = cur_sum / (n - 1);
// floor(average)
ll ans = get_operation(ave, cur_sum, idx);
if (cur_sum % (n - 1) != 0) {
// ceil(average)
ans = min(ans, get_operation(ave + 1, cur_sum, idx));
}
return ans;
};
ll ans = 1e18;
for (int i = 0; i < n; ++i) {
// 枚举每个数作为配对数
ans = min(ans, get_ans(i));
}
cout << ans << "\n";
return 0;
}
python
import math
n = int(input())
a = list(map(int, input().split()))
s = sum(a)
# sum % n == 0,则 a[i] 都可以改为 ave = sum / n
if sum(a) % n == 0:
ave = s // n
ans = sum(abs(x - ave) for x in a) // 2
print(ans)
exit()
a.sort()
pre = [0] * (n + 1)
for i in range(n):
pre[i + 1] = pre[i] + a[i]
def get_operation(ave, cur_sum, idx):
l = 0
r = n - 1
while l < r:
mid = (l + r + 1) // 2
if a[mid] < ave:
l = mid
else:
r = mid - 1
lt_sum = 0
lt_cnt = 0
gt_sum = 0
gt_cnt = 0
if idx > l:
lt_sum = pre[l + 1]
lt_cnt = l + 1
gt_sum = cur_sum - lt_sum
gt_cnt = n - 1 - lt_cnt
else:
gt_sum = pre[n] - pre[l + 1]
gt_cnt = n - (l + 1)
lt_sum = cur_sum - gt_sum
lt_cnt = n - 1 - gt_cnt
x = ave * lt_cnt - lt_sum
y = gt_sum - ave * gt_cnt
return max(x, y)
def get_ans(idx):
cur_sum = s - a[idx]
ave = cur_sum // (n - 1)
ans = get_operation(ave, cur_sum, idx)
if cur_sum % (n - 1) != 0:
ans = min(ans, get_operation(ave + 1, cur_sum, idx))
return ans
ans = math.inf
for i in range(n):
ans = min(ans, get_ans(i))
print(ans)
Java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
long sum = 0;
for (int i = 0; i < n; ++i) {
a[i] = scanner.nextInt();
sum += a[i];
}
// sum % n == 0,则 a[i] 都可以改为 ave = sum / n
if (sum % n == 0) {
long ans = 0;
long ave = sum / n;
for (int i = 0; i < n; ++i) {
ans += Math.abs(a[i] - ave);
}
System.out.println(ans / 2);
return;
}
Arrays.sort(a);
long[] pre = new long[n + 1];
for (int i = 0; i < n; ++i) {
pre[i + 1] = pre[i] + a[i];
}
long ans = Long.MAX_VALUE;
for (int i = 0; i < n; ++i) {
ans = Math.min(ans, getAns(a, pre, sum, n, i));
}
System.out.println(ans);
}
private static long getOperation(long ave, long curSum, int[] a, long[] pre, int n, int idx) {
int l = 0, r = n - 1;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (a[mid] < ave) l = mid;
else r = mid - 1;
}
long ltSum = 0;
int ltCnt = 0;
long gtSum = 0;
int gtCnt = 0;
if (idx > l) {
ltSum = pre[l + 1];
ltCnt = l + 1;
gtSum = curSum - ltSum;
gtCnt = n - 1 - ltCnt;
} else {
gtSum = pre[n] - pre[l + 1];
gtCnt = n - (l + 1);
ltSum = curSum - gtSum;
ltCnt = n - 1 - gtCnt;
}
long x = ave * ltCnt - ltSum;
long y = gtSum - ave * gtCnt;
return Math.max(x, y);
}
private static long getAns(int[] a, long[] pre, long sum, int n, int idx) {
long curSum = sum - a[idx];
long ave = curSum / (n - 1);
long ans = getOperation(ave, curSum, a, pre, n, idx);
if (curSum % (n - 1) != 0) {
ans = Math.min(ans, getOperation(ave + 1, curSum, a, pre, n, idx));
}
return ans;
}
}