UVA11300 Spreading the Wealth

题目大意

圆桌旁边坐着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 A1x1+x2=M
稍微整理一下可以得到:
x 2 = x 1 + M − A 1 x_2=x_1+M-A_1 x2=x1+MA1
同样,我们对2号点分析,有:
x 3 = M − A 2 + x 2 x_3=M-A_2+x_2 x3=MA2+x2
为了将变量统一,方便之后求极值,我们将x2用x1来替代,得到:
x 3 = x 1 + 2 M − A 1 − A 2 x_3=x_1+2M-A_1-A_2 x3=x1+2MA1A2
我们可以发现 x i = x 1 − C x_i=x_1-C xi=x1C(其中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=Ci1+AiM
这样我们的目的就变成了求如下式子的最大值:
∑ 1 n ∣ x i − C i ∣ \sum_1^n|x_i-C_i| 1nxiCi
接下来就是非常熟悉的绝对值不等式了,直接取中间值即可。

参考自《算法竞赛入门经典训练指南》(蓝书)

附上代码:

#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);

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值