AcWing 1843. 圆形牛棚 含y总的优化版本的详细公式推导

题目

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

牛棚内部有 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 个牛棚为起点,每头牛到其他牛棚的距离

r1r2r3rn
012n-1
n-101n-2
1230

设所经过的距离分别为 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

设 ri = ri - r
设 si为 ri 的前缀和
s0 = 0
即si = (r1 - r) + (r2 - r) + (r3 - r) + … + (ri - r)
si = r1 + r2 + r3 + … + ri
所以 ,对于任何 k+1 均有 sk+1 - s1 ≥ 0

当推广到 pk 为最小值时,如下图
在这里插入图片描述
则要满足对于任意 i,且 i 在 k 为出口的长度为 n 的范围内,
si - sk-1 ≥ 0 成立
即 sk-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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值