圆形牛棚
圆形牛棚
作为当代建筑的爱好者,农夫约翰建造了一个完美圆环形状的新牛棚。
牛棚内部有个房间,围成一个环形,按顺时针编号为,所有相邻房间之间的距离均为。
每个房间都既有通向相邻两个房间的门,也有通向牛棚外部的门。
约翰想让第个房间内恰好有头牛。
为了让奶牛们有序的进入牛棚,他计划打开一个外门,让牛从该门进入。
然后,每头牛顺时针(即当时,第个房间只能走到第个房间;当时,第个房间只能走到第个房间)穿过房间,直到到达合适的房间为止。
约翰希望通过合理选择打开的门,使得所有奶牛的行走距离之和尽可能小(这里只考虑每头牛进入牛棚以后的行走距离)。
请确定他的奶牛需要行走的最小总距离。
输入格式
第一行包含整数。
接下来行,包含,...,。
输出格式
输出所有奶牛需要行走的最小总距离。
数据范围
,
输入样例:
5 4 7 8 6 4
输出样例:
48
样例解释
最佳方案是让奶牛们从第二个房间进入。
解题思路
题意大概就是,选择一个房间作为入口,则代价就是每个房间牛的数量乘上该房间到入口房间的距离。
例如,如果我们选择作为入口,则代价大小为。
如果我们选择作为入口,则代价大小为。以此类推。
所以我们有个最朴素的做法,就是枚举所有牛棚将其选择作为入口,然后总代价又可以通过的复杂度算出来,所以整个算法的时间复杂度为。
需要注意的是需要对每个牛棚的下标编号进行取模。当枚举到下标时,因为顺时针走动,因此下一个牛棚的下标编号应该为。
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1010; 6 7 int a[N]; 8 9 int main() { 10 int n; 11 scanf("%d", &n); 12 for (int i = 0; i < n; i++) { 13 scanf("%d", a + i); 14 } 15 16 int ans = 5e7; 17 for (int i = 0; i < n; i++) { 18 int ret = 0; 19 for (int j = 1; j < n; j++) { 20 ret += a[(i + j) % n] * j; 21 } 22 ans = min(ans, ret); 23 } 24 25 printf("%d", ans); 26 27 return 0; 28 }
下面进行优化,讲讲时间复杂度为的算法思路和实现。
我们先把个牛棚列出来,同时把每个牛棚到入口的距离列出来。
下面我们定义为选择为入口的总代价,即。
接下来我们考虑一下怎么算,也就是怎么用来表示。可以发现,在上图中,用第行减去第行就可以得到的表达式,即这里定义。
同理可得
以此类推,我们可以发现有两种表达式,
其中对于第一种表达式,我们可以发现每一种状态都可以用上一种状态来表示,一共有种状态(选择个不同的牛棚作为入口),然后又因为一定存在其中一种代价最小的状态,因此我们把种状态通过迭代的方式枚举一遍,就可以找到最小的那种状态,这个状态对应的值就是最小代价。
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1010; 6 7 int r[N]; 8 9 int main() { 10 int n; 11 scanf("%d", &n); 12 13 int sum = 0, p = 0; 14 for (int i = 0; i < n; i++) { 15 scanf("%d", r + i); 16 sum += r[i]; 17 p += r[i] * i; 18 } 19 20 int ret = 2e9; 21 for (int i = 0; i < n; i++) { 22 p += n * r[i] - sum; 23 ret = min(ret, p); 24 } 25 26 printf("%d", ret); 27 28 return 0; 29 }
接下来我们对第二种表达式进行更深一步的挖掘。
首先在这个表达式中,我们是选择作为入口的,也就是选择作为初始状态,事实上我们可以选择任意一个作为初始状态,因为这是一个环,每一个点的地位都是等同的。也就是说,无论我们选择的是还是还是其他作为初始状态,都是可以推出的。
现在我们考虑一下,我们应该怎么选择,使得初始状态就是对应最小代价的那个状态。
现在我们就假设就是那个最小代价的状态。如果P_{0}是最小代价,那么对于任意一个的,都应该满足(所有的状态都满足大于等于)。
因为有,这就等价于。
进行移项和化简上式中,由于大于,所以可以消去。同时定义,即平均值。
这里的初始状态为,那么对于任意的,上面的表达式中的每一项的和要满足大于等于。更一般的情况,等价于我们要找的初始状态为,满足就是最小的代价,对于任意的都要满足表达式。
我们把环变成一条链,线段的长度为环的两倍,也就是。环上从任意一点走长度为的一段,等价于从线段中前一半长度的区间中找一个点,划出长度为的一段。
对于表达式式要成立,相当于在这个长度为的区间上,任意一个的前缀和都要大于等于。这里记,所以这就等价于在这个长度为的区间上,任意一个的前缀和都要大于等于。需要注意的是,现在线段的长度为环的两倍,不需要再取模了。
所以可以在的区间定义前缀和(前缀和的下标从开始)。等价于在这个长度为区间上任意一个点的前缀和满足。
其中,对于点,。
所以只要满足是最小值,那么就可以保证在长度为的区间,恒成立。因为在一个集合里面,集合中的任意一个元素减去集合中最小的那个元素,得到的值一定是大于等于的。
现在我们整理一下思路。
首先,我们先把这个环延长为长度为的线段,然后算一下,接着算前缀和数组,在前缀和数组中找到一个满足是最小的那个值(在区间中),那么我们从开始的,长度为的线段,即以为入口的代价最小。代码思路同上。
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 2010; 6 7 int r[N]; 8 double s[N]; 9 10 int main() { 11 int n; 12 scanf("%d", &n); 13 14 double sum; 15 for (int i = 1; i <= n; i++) { 16 scanf("%d", r + i); 17 r[n + i] = r[i]; 18 sum += r[i]; 19 } 20 21 double avg = sum / n; 22 for (int i = 1; i <= 2 * n; i++) { 23 s[i] = s[i - 1] + r[i] - avg; 24 } 25 26 int k = 0; 27 for (int i = 0; i < n; i++) { 28 if (s[i] < s[k]) k = i; 29 } 30 k++; 31 32 int ret = 0; 33 for (int i = 0; i < n; i++) { 34 ret += i * r[k + i]; 35 } 36 printf("%d", ret); 37 38 return 0; 39 }
参考资料
AcWing 1843. 圆形牛棚(寒假每日一题2022):https://www.acwing.com/video/3687/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/15851398.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效