上课睡觉
上课睡觉
有 堆石子,每堆的石子数量分别为 。
你可以对石子堆进行合并操作,将两个相邻的石子堆合并为一个石子堆,例如,如果 ,合并第 堆石子,则石子堆集合变为 。
我们希望通过尽可能少的操作,使得石子堆集合中的每堆石子的数量都相同。
请你输出所需的最少操作次数。
本题一定有解,因为可以将所有石子堆合并为一堆。
输入格式
第一行包含整数 ,表示共有 组测试数据。
每组数据第一行包含整数 。
第二行包含 个整数 。
输出格式
每组数据输出一行结果。
数据范围
,
,
,
,
每个输入所有 之和不超过 。
输入样例:
3 6 1 2 3 1 1 1 3 2 2 3 5 0 0 0 0 0
输出样例:
3 2 0
样例解释
第一组数据,只需要用 个操作来完成:
1 2 3 1 1 1 -> 3 3 1 1 1 -> 3 3 2 1 -> 3 3 3
第二组数据,只需要用 个操作来完成:
2 2 3 -> 2 5 -> 7
第三组数据,我们什么都不需要做。
解题思路
先给出我一开始的思路。题目要求每次只能合并相邻的两堆石子,并且最终每堆的价值是相同的,这个问题就等价于能否将序列分成若干段,使得每一段的和都等于同一个数。
因此容易想到先枚举出第一段,然后看能不能把剩余的部分分成与第一段总和相同的若干段,这种做法的时间复杂度为。
假设第一段是区间,第一段的总和是,整个序列的和为,那么区间就应该被划分为段,并且还要满足。假设有序列的前缀和数组,那么这个条件就等价于,而区间恰好被划分成段,就等价于在中存在个使得依次是的倍、倍、......、倍。
因此现在我们先预处理出前缀和并开个哈希表记录所有下标的前缀,然后枚举出第一段的右端点,然后再枚举判断的每个倍数是否都存在,即从从开始枚举,直到,每一个都有对应的前缀和存在。
有公式
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10, M = 1e6 + 10; 5 6 int s[N]; 7 bool vis[M]; 8 9 void solve() { 10 int n; 11 scanf("%d", &n); 12 memset(vis, 0, sizeof(vis)); 13 for (int i = 1; i <= n; i++) { 14 scanf("%d", s + i); 15 s[i] += s[i - 1]; 16 vis[s[i]] = true; 17 } 18 int ret = n; 19 for (int i = 1; i <= n; i++) { 20 if (s[i] == 0) { // 跳过前i个均为0的情况 21 if (s[n] == 0) { // 如果整个序列的数都为0,那么不需要划分 22 ret = 0; 23 break; 24 } 25 } 26 else if (s[n] % s[i] == 0) { // 满足s[i] | s[n] 27 bool flag = true; 28 for (int j = 1; j <= s[n] / s[i]; j++) { 29 if (!vis[s[i] * j]) { // 每个s[i]的倍数都要存在 30 flag = false; 31 break; 32 } 33 } 34 if (flag) ret = min(ret, n - s[n] / s[i]); 35 } 36 } 37 printf("%d\n", ret); 38 } 39 40 int main() { 41 int t; 42 scanf("%d", &t); 43 while (t--) { 44 solve(); 45 } 46 47 return 0; 48 }
再给出y总的做法。如果存在一组解,意味着等式成立,其中是每一段的和,是段的数量,是整个序列的和,可以发现和都是的约数。因此我们可以枚举,满足,那么,然后看看能不能把序列划分成每一段的和均为,如果可以那么答案就是。
在的范围内,一个数最多有个约数,因此时间计算量大概是。
补充:估算的约数个数。先考虑这个数所有的约数,中是的倍数的数有个(意味着这些数中含有因子的数量),是的倍数的数有个,......,是的倍数的数有,那么中所有数的约数个数的和就是,因此平均下来每个数含有的约数个数大概有个。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10; 5 6 int n; 7 int a[N]; 8 9 bool check(int sum) { 10 int s = 0; 11 for (int i = 0; i < n; i++) { 12 s += a[i]; 13 if (s > sum) return false; 14 if (s == sum) s = 0; 15 } 16 // 这里也可以直接写return true; 如果能运行到这里那么一定满足s==0 17 return s == 0; 18 } 19 20 void solve() { 21 scanf("%d", &n); 22 int sum = 0; 23 for (int i = 0; i < n; i++) { 24 scanf("%d", a + i); 25 sum += a[i]; 26 } 27 for (int i = n; i; i--) { 28 if (sum % i == 0 && check(sum / i)) { 29 printf("%d\n", n - i); 30 break; 31 } 32 } 33 } 34 35 int main() { 36 int t; 37 scanf("%d", &t); 38 while (t--) { 39 solve(); 40 } 41 42 return 0; 43 }
参考资料
AcWing 4366. 上课睡觉(寒假每日一题2023):https://www.acwing.com/video/4576/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17028629.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效