ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

圆形牛棚

2022-01-28 12:33:00  阅读:165  来源: 互联网

标签:... right cdot 牛棚 int 圆形 overset left


圆形牛棚

作为当代建筑的爱好者,农夫约翰建造了一个完美圆环形状的新牛棚。

牛棚内部有$n$个房间,围成一个环形,按顺时针编号为$1 \sim n$,所有相邻房间之间的距离均为$1$。

每个房间都既有通向相邻两个房间的门,也有通向牛棚外部的门。

约翰想让第$i$个房间内恰好有$r_{i}$头牛。

为了让奶牛们有序的进入牛棚,他计划打开一个外门,让牛从该门进入。

然后,每头牛顺时针(即当$i<n$时,第$i$个房间只能走到第$i+1$个房间;当$i=n$时,第$i$个房间只能走到第$1$个房间)穿过房间,直到到达合适的房间为止。

约翰希望通过合理选择打开的门,使得所有奶牛的行走距离之和尽可能小(这里只考虑每头牛进入牛棚以后的行走距离)。

请确定他的奶牛需要行走的最小总距离。

输入格式

第一行包含整数$n$。

接下来$n$行,包含$r_{1}$,...,$r_{n}$。

输出格式

输出所有奶牛需要行走的最小总距离。

数据范围

$3 \leq n \leq 1000$,

$1 \leq r_{i} \leq 100$

输入样例:

5
4
7
8
6
4

输出样例:

48

样例解释

最佳方案是让奶牛们从第二个房间进入。

 

解题思路

  题意大概就是,选择一个房间作为入口,则代价就是每个房间牛的数量乘上该房间到入口房间的距离。

  例如,如果我们选择$r_{0}$作为入口,则代价大小为$r_{0} \cdot 0 + r_{1} \cdot 1 + r_{2} \cdot 2 + ... + r_{n - 1} \cdot \left( {n - 1} \right)$。

  如果我们选择$r_{1}$作为入口,则代价大小为$r_{1} \cdot 0 + r_{2} \cdot 1 + ... + r_{n - 1} \cdot \left( {n - 2} \right) + r_{0} \cdot \left( {n - 1} \right)$。以此类推。

  所以我们有个最朴素的做法,就是枚举所有牛棚将其选择作为入口,然后总代价又可以通过$O\left( n \right)$的复杂度算出来,所以整个算法的时间复杂度为$O\left( n^{2} \right)$。

  需要注意的是需要对每个牛棚的下标编号进行取模$n$。当枚举到下标$n - 1$时,因为顺时针走动,因此下一个牛棚的下标编号应该为$0$。

  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 }

  下面进行优化,讲讲时间复杂度为$O\left( n \right)$的算法思路和实现。

  我们先把$n$个牛棚列出来,同时把每个牛棚到入口的距离列出来。

  下面我们定义$P_{0}$为选择$r_{0}$为入口的总代价,即$P_{0} = r_{0} \cdot 0 + r_{1} \cdot 1 + ... + r_{n - 2} \cdot \left( {n - 2} \right) + r_{n - 1} \cdot \left( {n - 1} \right)$。

  接下来我们考虑一下$P_{1}$怎么算,也就是怎么用$P_{0}$来表示。可以发现,在上图中,用第$2$行减去第$1$行就可以得到$P_{1} - P_{0}$的表达式,即$$\begin{align*} P_{1} &= P_{0} + \left( {n - 1} \right) \cdot r_{0} - \left( {r_{1} + r_{2} + ... + r_{n - 1}} \right) \\ &= P_{0} + n \cdot r_{0} - \left( {r_{0} + r_{1} + r_{2} + ... + r_{n - 1}} \right) \\ &= P_{0} + n \cdot r_{0} - s \end{align*}$$这里定义$s = r_{0} + r_{1} + ... + r_{n - 1}$。

  同理可得$$\begin{align*} P_{2} &= P_{1} + n \cdot r_{1} - s \\ &= P_{0} + n \cdot \left( {r_{0} + r_{1}} \right) - 2s \end{align*}$$

  以此类推,我们可以发现$P_{i}$有两种表达式,$$P_{i} = P_{i - 1} + n \cdot r_{i - 1} - s$$$$P_{i} = P_{0} + n \cdot \left( {r_{0}{+ r}_{1}{+ ... + r}_{i - 1}} \right) - i \cdot s$$

  其中对于第一种表达式,我们可以发现每一种状态都可以用上一种状态来表示,一共有$n$种状态(选择$n$个不同的牛棚作为入口),然后又因为一定存在其中一种代价最小的状态,因此我们把$n$种状态通过迭代的方式枚举一遍,就可以找到最小的那种状态,这个状态对应的值就是最小代价。

  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 }

  接下来我们对第二种表达式进行更深一步的挖掘。

  首先在这个表达式中,我们是选择$r_{0}$作为入口的,也就是选择$P_{0}$作为初始状态,事实上我们可以选择任意一个$P_{i}$作为初始状态,因为这是一个环,每一个点的地位都是等同的。也就是说,无论我们选择的是$P_{0}$还是$P_{1}$还是其他作为初始状态,都是可以推出$P_{0} \sim P_{n - 1}$的。

  现在我们考虑一下,我们应该怎么选择,使得初始状态就是对应最小代价的那个状态。

  现在我们就假设$P_{0}$就是那个最小代价的状态。如果P_{0}是最小代价,那么对于任意一个的$P_{i}$,都应该满足$P_{i} - P_{0} \geq 0$(所有的状态都满足大于等于$0$)。

  因为有$P_{i} = P_{0} + n \cdot \left( {r_{0}{+ r}_{1}{+ ... + r}_{i - 1}} \right) - i \cdot s$,这就等价于$n \cdot \left( {r_{0}{+ r}_{1}{+ ... + r}_{i - 1}} \right) - i \cdot s \geq 0$。

  进行移项和化简$$\begin{align*} n \cdot \left( {r_{0}{+ r}_{1}{+ ... + r}_{i - 1}} \right) &\geq i \cdot s \\ \frac{r_{0} + r_{1} + ... + r_{i - 1}}{i} &\geq \frac{s}{n} \\ \frac{r_{0} + r_{1} + ... + r_{i - 1}}{i} &\geq \overset{-}{r} \\ \frac{r_{0} + r_{1} + ... + r_{i - 1}}{i} - \overset{-}{r} &\geq 0 \\ \frac{r_{0} + r_{1} + ... + r_{i - 1} - i \cdot \overset{-}{r}}{i} &\geq 0 \\ \frac{\left( {r_{0} - \overset{-}{r}} \right) + \left( {r_{1} - \overset{-}{r}} \right) + ... + \left( {r_{i - 1} - \overset{-}{r}} \right)}{i} &\geq 0 \\ \left( {r_{0} - \overset{-}{r}} \right) + \left( {r_{1} - \overset{-}{r}} \right) + ... + \left( {r_{i - 1} - \overset{-}{r}} \right) &\geq 0 \end{align*}$$上式中,由于$i$大于$0$,所以可以消去。同时定义$\overset{-}{r} = \frac{s}{n}$,即平均值。

  这里的初始状态为$P_{0}$,那么对于任意的$i$,上面的表达式中的每一项的和要满足大于等于$0$。更一般的情况,等价于我们要找的初始状态为$k$,满足$P_{k}$就是最小的代价,对于任意的$i$都要满足表达式$\left( {r_{k\% n} - \overset{-}{r}} \right) + \left( {r_{{({k + 1})}\% n} - \overset{-}{r}} \right) + ... + \left( {r_{{({k + i - 1})}\% n} - \overset{-}{r}} \right) \geq 0$。

  我们把环变成一条链,线段的长度为环的两倍,也就是$2n$。环上从任意一点走长度为$n$的一段,等价于从线段中前一半长度的区间中找一个点,划出长度为$n$的一段。

  对于表达式式$\left( {r_{k\% n} - \overset{-}{r}} \right) + \left( {r_{{({k + 1})}\% n} - \overset{-}{r}} \right) + ... + \left( {r_{{({k + i - 1})}\% n} - \overset{-}{r}} \right) \geq 0$要成立,相当于在这个长度为$n$的区间上,任意一个$r_{i} - \overset{-}{r}$的前缀和都要大于等于$0$。这里记$r_{i} - \overset{-}{r} = {r'}_{i}$,所以这就等价于在这个长度为$n$的区间上,任意一个${r'}_{i}$的前缀和都要大于等于$0$。需要注意的是,现在线段的长度为环的两倍,不需要再取模了。

  所以可以在$2n$的区间定义前缀和${s'}_{i} = {r'}_{1} + {r'}_{2} + ... + {r'}_{i}$(前缀和的下标从$1$开始)。等价于在这个长度为$n$区间上任意一个点的前缀和满足${s'}_{i} - {s'}_{k - 1} \geq 0$。

  其中,对于点$n$,${s'}_{n} = {r'}_{1} + {r'}_{2} + ... + {r'}_{n} = s - n \cdot \overset{-}{r} = s - s = 0$。

  所以只要满足${s'}_{k - 1}$是最小值,那么就可以保证在长度为$n$的区间,${s'}_{i} - {s'}_{k - 1} \geq 0$恒成立。因为在一个集合里面,集合中的任意一个元素减去集合中最小的那个元素,得到的值一定是大于等于$0$的。

  现在我们整理一下思路。

  首先,我们先把这个环延长为长度为$2n$的线段,然后算一下${r'}_{i}$,接着算前缀和数组,在前缀和数组中找到一个$k-1$满足${s'}_{k-1}$是最小的那个值($k-1$在区间$0 \sim n-1$中),那么我们从$k$开始的,长度为$n$的线段,即以$k$为入口的代价最小。代码思路同上。

  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/

标签:...,right,cdot,牛棚,int,圆形,overset,left
来源: https://www.cnblogs.com/onlyblues/p/15851398.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有