分金币
洛谷P3156
技术统计
难度 提高+/省选-
用时 10min
提交次数 1
unaccept 次数 0
ac次数 1
题意概括
圆桌上坐着n个人,每人有一定数量的金币,金币总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。
数据范围
3 ≤ n ≤ 100000 , 金 币 数 ≤ 1 0 9 3 \le n \le 100000,金币数\le 10^9 3≤n≤100000,金币数≤109
解法一、
知识点
- 数学题
- 数学题
- 数学题(重要的事情说三遍)
解法详解
很显然的是,我们需要求average(平均值)。我们设一个前缀和sum[i],为前i个人转手的金币的数量之和,显然对于第i个人,他转手的金币数为sum[i+1]-sum[i]。我们可以求出来sum[1…i]-average的值。到这里,我们求过的东西仿佛与答案的关系并不大。因为我们需要求的是转手的金币的和的最小值,而转手的金币的数量是一定的,那么我们可以默认,每个人转手的方式都是最优的,那么有S=sum[1…n]-sum[i]是可能的答案。如何让S最大呢?根据数学推导(懒),sum[mid]==sum[i]时S有最大值。到这里本题的核心就结束了,求sum[mid]只需要排一下序,然后使mid=(1+n+1)>>1就好了。
坑点
- 因为1+n的奇偶性无从得知,所以我们可以根据c++向下取整的特性,让mid=(1+n+1)/2
- 一定要开long long 啊,毕竟金币数*n=10^14。
数学证明
我们设i+1给i的金币为p[i],(pi可正可负),ave为平均值,那么ans=|p[1]|+|p[2]|+…+|p[n]|,我们要求的是ans的最小值。
很显然,ave+p[i]=p[i+1]+a[i] (这里很妙,我不给出为什么,可以散发一下你们的神犇之力想想),ave-a[i]=p[i+1]-p[i],我们设w[i]=ave-a[i],s[i]=w[1]+w[2]+w[3]+…+w[i].
所以p[i]=s[i-1]-p[1]
设x=s[i-1]-p[1]
那么ans=|0-x|+|s[1]-x|+…|s[n-1]-x|;
所以若要ans的值最小,有x=s[mid](x为s[]的中位数)
综上所述:
a
n
s
=
∑
i
=
1
n
∣
s
[
i
]
−
s
[
m
i
d
]
∣
ans=\sum_{i=1}^{n}|s[i]-s[mid]|
ans=i=1∑n∣s[i]−s[mid]∣
PS:s即为sum。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define maxn 1000100
using namespace std;
inline ll read()
{
ll f=1,k=0;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return f*k;
}
inline void write(ll x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
ll n,m,aver;
ll a[maxn];
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
aver+=a[i];
}
aver/=n;
for(int i=1;i<=n;i++)
{
a[i]-=aver;//节约空间,不想再开一个sum数组了
a[i]+=a[i-1];
}
sort(a+1,a+n+1);
aver=0;
ll mid=(2+n)>>1;
for(int i=1;i<=n;i++) aver+=abs(a[i]-a[mid]);
write(aver);
system("puse");
return 0;
}
解法二、
听dalao们说可以用费用流求,然而数据范围不允许dalao们散发神犇的光辉
类似题目
- 分糖果(双倍经验)
- 均分纸牌