题目大意
圆桌旁边坐着n个人,每个人有一定数量的金币,金币的总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数量相等。您的任务是求出被转手的金币的数量的最小值。
分析:
这是一道思维题,关键在于分析每个点的状态,列出等价方程,找到其中的递推关系,再利用绝对值不等式求解即可。
先在我们假设有n个点,依次标号为1,2…,n-1,n。其中每个点的初始金币有Ai个,现在要使每个点的金币数相同,那么这个金币的数量便是所有初始金币数量的和再去除以n(即平分),而我们转手的最小金额便是每个点向外转出金币次数的总和,而每个要转出金币的点会先向他的两个邻居,或是一个邻居转出金币,考虑到直接对每个点向两个邻居方向的转移的分析较为困难,我们不妨规定每个点只能向一边转移,而另一边的转移,则转化他的邻居的转移,这是等价的。例如2号点,我们规定他向左侧(即1号点)的转移为x2,如果x2 > 0,则2号点向1号点转移x2个金币,否则则为1号点向2号点转移。那么我们的目的就是求出 ∑ 1 n \sum_1^n ∑1n x i x_i xi的最小值。
现在我们来分析如何获得
x
i
x_i
xi之间的关系,先对一号点分析,因为在转移后,我们最后的金币数额是固定的,我们记作M,那么我们可以列出如下等式:
A
1
−
x
1
+
x
2
=
M
A_1-x_1+x_2=M
A1−x1+x2=M
稍微整理一下可以得到:
x
2
=
x
1
+
M
−
A
1
x_2=x_1+M-A_1
x2=x1+M−A1
同样,我们对2号点分析,有:
x
3
=
M
−
A
2
+
x
2
x_3=M-A_2+x_2
x3=M−A2+x2
为了将变量统一,方便之后求极值,我们将x2用x1来替代,得到:
x
3
=
x
1
+
2
M
−
A
1
−
A
2
x_3=x_1+2M-A_1-A_2
x3=x1+2M−A1−A2
我们可以发现
x
i
=
x
1
−
C
x_i=x_1-C
xi=x1−C(其中C为常数)
因为每一个xi 都只与相邻的前两个有关(即使是最后一个,由于在环上,所以也是相邻的),而如果前两个xi都能用x1 -C来表示,那么当前的点也一定可以用x1 来表示,而我们知道,一旦得到了n-1个点的关系,第n个点的金币转移也一定确定了的(稍微思考下)。
当然常数C也存在递推关系即
C
i
=
C
i
−
1
+
A
i
−
M
C_i=C_{i-1} +A_i-M
Ci=Ci−1+Ai−M
这样我们的目的就变成了求如下式子的最大值:
∑
1
n
∣
x
i
−
C
i
∣
\sum_1^n|x_i-C_i|
∑1n∣xi−Ci∣
接下来就是非常熟悉的绝对值不等式了,直接取中间值即可。
参考自《算法竞赛入门经典训练指南》(蓝书)
附上代码:
#include <iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<cmath>
#include<queue>
#include<list>
#include<map>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int p = 1e9+7;
const int N = 2e3+5;
typedef pair<ll,ll> PII;
ll n,m,t;
ll k;
ll a[N];
ll d[N];
void solve(){
while(~scanf("%lld",&n)){
ll tot = 0;
for(int i = 1; i <= n; ++i){
scanf("%lld",&a[i]);
tot+=a[i];
}
ll M = tot/n;
for(int i = 1; i <= n; ++i){
d[i] = d[i-1] + a[i] - M;
}
sort(d+1, d+1+n);
ll ans = 0;
ll x1 = d[n>>1];
for(int i = 1;i <= n; ++i)ans += abs(x1-d[i]);
printf("%lld\n",ans);
}
}
int main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
//cout.tie(0);
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
// scanf("%lld",&t);
// while(t--)
solve();
fclose(stdin);
fclose(stdout);
}