寒假算法每日一题2022.12.28
4366. 上课睡觉 - AcWing题库
有 N 堆石子,每堆的石子数量分别为 a1,a2,…,aN。
你可以对石子堆进行合并操作,将两个相邻的石子堆合并为一个石子堆,例如,如果 a=[1,2,3,4,5],合并第 2,3 堆石子,则石子堆集合变为 a=[1,5,4,5]。
我们希望通过尽可能少的操作,使得石子堆集合中的每堆石子的数量都相同。
请你输出所需的最少操作次数。
本题一定有解,因为可以将所有石子堆合并为一堆。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N。
第二行包含 N 个整数 a1,a2,…,aN。
输出格式
每组数据输出一行结果。
数据范围
1
≤
T
≤
10
,
1≤T≤10,
1≤T≤10,
1
≤
N
≤
1
0
5
,
1≤N≤10^5,
1≤N≤105,
0
≤
a
i
≤
1
0
6
,
0≤ai≤10^6,
0≤ai≤106,
∑
i
=
1
n
a
i
≤
1
0
6
,
\sum_{i=1}^n{ai}\ \leq 10^6,
∑i=1nai ≤106,
每个输入所有 N 之和不超过
1
0
5
10^5
105。
输入样例:
3
6
1 2 3 1 1 1
3
2 2 3
5
0 0 0 0 0
输出样例:
3
2
0
样例解释
第一组数据,只需要用 3 个操作来完成:
1 2 3 1 1 1
-> 3 3 1 1 1
-> 3 3 2 1
-> 3 3 3
第二组数据,只需要用 2 个操作来完成:
2 2 3
-> 2 5
-> 7
第三组数据,我们什么都不需要做。
解题思路:
我们分析得到,设sum为石子的总数量,cnt能分成每堆石子的数量都相同的石子数量,我们可以知道cnt和分成的堆数都是sum的约数。即 cnt 能整除 sum。因为每次操作只能是相邻的两堆合并,所以操作次数为 初始堆数 - 分成的堆数,即 n - (sum / cnt)。我们要使得操作次数最小,即求符合题意的cnt的最小值。
那么如何确定符合题意的cnt的最小值呢?
我们可以从小到大枚举sum的约数cnt,对与每一个cnt,我们顺序枚举原始石子的堆数,并判断是否能分成每一堆都为cnt。
import java.util.Scanner;
// AcWing4366 上课睡觉
public class Day1228 {
static final int N = 100010;
static int[] w = new int[N];
static int n;
// 判断是否分成每一堆石子数量都为cnt
public static boolean check(int cnt) {
for(int i = 0,s = 0; i < n; i++) {
s+=w[i];
if(s > cnt) {
// 出现矛盾,返回false
return false;
} else if(s == cnt) {
s = 0;
}
// 这里无需判断s < cnt的情况,因为 cnt为 sum的约数,遍历到最后如果没有矛盾,必然成立
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while(T-- > 0) {
int sum = 0; // 所有石子的总和
n = sc.nextInt();
for(int i = 0; i < n;i++) {
w[i] = sc.nextInt();
sum+=w[i];
}
// 从大到小枚举可能的堆数(sum的约数),则每一堆石子数量就从小到大枚举
for(int i = n; i >= 1; i--) {
if(sum % i == 0 && check(sum/i)) {
// 操作次数 = n(初始堆数) - i(堆数)
System.out.println(n - i);
break;
}
}
}
}
}