题目
作为当代建筑的爱好者,农夫约翰建造了一个完美圆环形状的新牛棚。
牛棚内部有 n 个房间,围成一个环形,按顺时针编号为 1∼n。
每个房间都既有通向相邻两个房间的门,也有通向牛棚外部的门。
约翰想让第 i 个房间内恰好有 ri 头牛。
为了让奶牛们有序的进入牛棚,他计划打开一个外门,让牛从该门进入。
然后,每头牛顺时针穿过房间,直到到达合适的房间为止。
约翰希望通过合理选择打开的门,使得所有奶牛的行走距离之和尽可能小(这里只考虑每头牛进入牛棚以后的行走距离)。
请确定他的奶牛需要行走的最小总距离。
输入格式
第一行包含整数 n。
接下来 n 行,包含 r1,…,rn。
输出格式
输出所有奶牛需要行走的最小总距离。
数据范围
3 ≤ n ≤ 1000,
1 ≤ ri ≤100
输入样例:
5
4
7
8
6
4
输出样例:
48
样例解释
最佳方案是让奶牛们从第二个房间进入。
蒟蒻的方法(简单但low)
解题思路
如上图所示,输入数据是从一号棚顺时针输入,各需要 4 7 8 6 4 头牛
题目中计划打开一个外门,且只考虑牛进入牛棚后的距离,所以可以看作这 29 头牛最开始在同一个棚中
而相邻的门虽然相通,但是只能通过顺时针进入其他牛棚,所以上图中共有如下几种情况:
1.牛从 1 号门进入,即所有牛最初均在 1 号牛棚
d = 0 * 4 + 1 * 7 + 2 * 8 + 3 * 6 + 4 * 4 = 57
2.牛从 2 号门进入,即所有牛最初均在 2 号牛棚
d = 4 * 4 + 0 * 7 + 1 * 8 + 2 * 6 + 3 * 4 = 48
3.牛从 3 号门进入,即所有牛最初均在 3 号牛棚
d = 3 * 4 + 4 * 7 + 0 * 8 + 1 * 6 + 2 * 4 = 54
4.牛从 4 号门进入,即所有牛最初均在 4 号牛棚
d = 2 * 4 + 3 * 7 + 4 * 8 + 0 * 6 + 1 * 4 = 65
5.牛从 5 号门进入,即所有牛最初均在 5 号牛棚
d = 1 * 4 + 2 * 7 + 3 * 8 + 4 * 6 + 0 * 4 = 66
注:相邻牛棚间的距离为 1
不难发现,上面这些式子可以化成如下的矩阵运算,开外侧门的顺序为顺时针(0 -> 1 -> 2 -> 3 -> 4):
[
57
48
54
65
66
]
=
[
4
7
8
6
4
]
∗
[
0
4
3
2
1
1
0
4
3
2
2
1
0
4
3
3
2
1
0
4
4
3
2
1
0
]
\begin{bmatrix}57 & 48 & 54 & 65 & 66\end{bmatrix}=\begin{bmatrix}4 & 7 & 8 & 6 & 4\end{bmatrix}*\begin{bmatrix}0 & 4 & 3 & 2 & 1 \\ 1 & 0 & 4 & 3 & 2 \\ 2 & 1 & 0 & 4 & 3 \\ 3 & 2 & 1 & 0 & 4 \\ 4 & 3 & 2 & 1 & 0 \end{bmatrix}
[5748546566]=[47864]∗⎣⎢⎢⎢⎢⎡0123440123340122340112340⎦⎥⎥⎥⎥⎤
由于我们只要求最小值,并不关心其顺序,所以矩阵可以转化成如下方便后续计算,即将开外门的顺序调整为逆时针(0 -> 4 -> 3 -> 2 -> 1):
[
57
66
65
54
48
]
=
[
4
4
6
8
7
]
∗
[
0
1
2
3
4
1
2
3
4
0
2
3
4
0
1
3
4
0
1
2
4
0
1
2
3
]
\begin{bmatrix}57 & 66 & 65 & 54 & 48\end{bmatrix}=\begin{bmatrix}4 & 4 & 6 & 8 & 7\end{bmatrix}*\begin{bmatrix}0 & 1 & 2 & 3 & 4 \\ 1 & 2 & 3 & 4 & 0 \\ 2 & 3 & 4 & 0 & 1 \\ 3 & 4 & 0 & 1 & 2 \\ 4 & 0 & 1 & 2 & 3 \end{bmatrix}
[5766655448]=[44687]∗⎣⎢⎢⎢⎢⎡0123412340234013401240123⎦⎥⎥⎥⎥⎤
设 i,j 下标的起始都为 0,第 i 个牛棚中所需要的牛为 xi
i 为牛棚的位置
j 可以看成是当前开的外侧的门相对于 0 号外门的逆时针偏移量
(i + j) 可以看作是第 i 个牛棚到距离 0 号位置逆时针 j 个距离的距离
由于是一个环,会绕回到原地,距离其实为0,但 (i + j) 会等于 n 所以距离为 (i + j) % n
例如 j = 1 时:
0 号位置相对于 4 号位置的距离为 (0 + 1) % 5 = 1
1 号位置相对于 4 号位置的距离为 (1 + 1) % 5 = 2
2 号位置相对于 4 号位置的距离为 (2 + 1) % 5 = 3
3 号位置相对于 4 号位置的距离为 (4 + 1) % 5 = 4
4 号位置相对于 4 号位置的距离为 (4 + 1) % 5 = 0
即程序中的 sum[j] += x * ((i + j) % n)
AC代码
Java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
int n = Integer.parseInt(in.readLine());
int[] sum = new int[n]; // 从不同入口进入所需要经过的路程
for(int i = 0; i < n; i++) {
int x = Integer.parseInt(in.readLine());
for(int j = 0; j < n; j++) sum[j] += x * ((i + j) % n);
}
Arrays.sort(sum);
out.print(sum[0]);
out.flush();
in.close();
}
}
c++
#include<iostream>
#include<algorithm>
using namespace std;
int sum[1001];
int main()
{
std::ios::sync_with_stdio(false);
int n = 0;
cin >> n;
for(int i = 0; i < n; i++)
{
int x = 0;
cin >> x;
for(int j = 0; j < n; j++) sum[j] += x * ((i + j) % n);
}
sort(sum, sum + n);
cout << sum[0] << endl;
}
y总的方法(高大上)
优化思路
这张表格还是上面的矩阵
以第 i 个牛棚为起点,每头牛到其他牛棚的距离
r1 | r2 | r3 | … | rn |
---|---|---|---|---|
0 | 1 | 2 | … | n-1 |
n-1 | 0 | 1 | … | n-2 |
… | … | … | … | … |
1 | 2 | 3 | … | 0 |
设所经过的距离分别为 p1,p2,p3,…,pn
设 s = r1 + r2 + r3 + … + rn
p1 = 0 * r1 + 1 * r2 + 2 * r3 + … + (n - 1) * rn
p2 = (n - 1) * r1 + 0 * r2 + 1 * r3 + … + (n - 2) * rn
p2 = p1 + n * r1 - (r1 + r2 + r3 + … + rn)
p2 = p1 + n * r1 - s
p3 = (n - 2) * r1 + (n - 1) * r2 + 0 * r3 + … + (n - 3) * rn
p3 = p1 + n * (r1 + r2) - 2 * ( r1 + r2 + r3 + … + rn)
p3 = p1 + n * (r1 + r2) - 2 * s
…
pk+1 = p1 + n * (r1 + r2 + r3 + … + rk) - k * s
假设 p1 为最小值,那么对于所有 pk+1 均有
pk+1 - p1 ≥ 0
n * (r1 + r2 + r3 + … + rk) - k * s ≥ 0
(r1 + r2 + r3 + … + rk) / k ≥ s / n
设 r = s / n
(r1 + r2 + r3 + … + rk) / k - r ≥ 0
((r1 - r) + (r2 - r) + (r3 - r) + … + (rk - r)) / k ≥ 0
(r1 - r) + (r2 - r) + (r3 - r) + … + (rk - r) ≥ 0
设 r’i = ri - r
设 s’i为 r’i 的前缀和
s’0 = 0
即s’i = (r1 - r) + (r2 - r) + (r3 - r) + … + (ri - r)
s’i = r’1 + r’2 + r’3 + … + r’i
所以 ,对于任何 k+1 均有 s’k+1 - s’1 ≥ 0
当推广到 pk 为最小值时,如下图
则要满足对于任意 i,且 i 在 k 为出口的长度为 n 的范围内,
s’i - s’k-1 ≥ 0 成立
即 s’k-1 是最小值
即 k 是入口
AC代码
Java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
int n = Integer.parseInt(in.readLine());
int res = 0;
double sum = 0; // 牛的总数
int[] x = new int[2 * n + 1]; // 第 i 个牛棚需要多少头牛
double[] s = new double[2 * n + 1]; // 牛棚的总房间数
for(int i = 1; i <= n; i++) {
x[i] = Integer.parseInt(in.readLine());
x[n + i] = x[i];
sum += x[i];
}
double avg = sum / n; // 求平均值 r
// 计算 ri - r 的前缀和
for(int i = 1; i <= 2 * n; i++) s[i] = s[i - 1] + x[i] - avg;
int k = 0;
for(int i = 1; i <= n; i++)
if(s[i] < s[k]) k = i; // 求前缀和的最小值的位置
k++; // 前缀和最小值的后一位为入口
for(int i = 0; i < n; i++) res += i * x[k + i]; // 计算距离
out.print(res);
out.flush();
in.close();
}
}
c++
#include <iostream>
using namespace std;
const int N = 2010;
int n; // 牛棚的总房间数
int r[N]; // 第 i 个牛棚需要多少头牛
double s[N]; // ri - r 的前缀和
int main()
{
cin >> n;
double sum = 0; // 牛的总数
for (int i = 1; i <= n; i ++ )
{
cin >> r[i];
r[n + i] = r[i];
sum += r[i];
}
double avg = sum / n; // 求平均值 r
for (int i = 1; i <= n * 2; i ++ )
s[i] = s[i - 1] + r[i] - avg; // 计算 ri - r 的前缀和
int k = 0;
for (int i = 1; i <= n; i ++ )
if (s[i] < s[k])
k = i; // 求前缀和的最小值的位置
k ++ ; // 前缀和最小值的后一位为入口
int res = 0;
for (int i = 0; i < n; i ++ )
res += i * r[k + i]; // 计算距离
printf("%d\n", res);
return 0;
}