zoukankan      html  css  js  c++  java
  • P2512 [HAOI2008]糖果传递

    传送门

    算法 : 瞎搞......

    这种题是真的恶心....

    以下为一堆的结论和证明...

    (自己口胡的,比较细,实在不想看也可以直接看结论)



    首先如果要让每个人最终的糖果一样多

    1.那么肯定最终每个人的糖果数量为 每个人糖果数量的平均数..(显然...)

    还有一个显然的结论:

    2.如果 a 把糖分给别人,那么 a 就不应该再收到糖

    不然就不可能是最优方法(a 还不如少分一点糖给别人,这样糖果的交换数量还更小)

    当然反过来也一样。

    3.如果 a 的糖果数量大于平均数,那么 a 一定要分糖给别人(由 1. 可得)

    并且 a 也不会再接受其他人的糖(由 2. 可得)

    反过来也是一样: 

    如果 a 的糖果数量小于平均数,那么 a 一定要从别人接受糖,并且 a 也不会分给其他人糖。

    所以有一种比较好的方法:对于每个人只要把多出来的糖传给下一个就好了

    如果是缺少糖就把需求传给别人,让别人把需求传下去,设多的是正数,那么需求就为负数,如果负数被抵消了就说明有正数把需求"填满"了,相当于有多出来的糖返回给缺少的人了....(这里可能需要一点网络流反向边的思想...)。

    (但是网络流是不可能的,数据太大,直接凉了)

    设第 i 个人 原本的糖果数量为 a[ i ],第 i 个人原本的糖果数量减去平均数量 为 b[ i ],(b[ i ] 可能为负数,表示 i 需要 abs( b[ i ] ) 个糖)

    那么最终就是要经过转移让所有的 b 都为 0 。

    先考虑第一个人 k 怎么分(因为是环形,所以每个人都可以是第一个人)

    由 3. 可得,要分 b[ k ] 个糖

    那要怎么分(可以左边给几个,右边给几个,也可以只给一边)

    方案太多不好搞

    但是要注意:4.分给两边其实相当于一边的某一个点把多出来的全部分给另一边....

    怎么描述呢:

      就是说 k 分给左边几个糖,分着分着,分到点 j 分完了,剩下的分给右边,最后分到 j 肯定也刚好分平均(由 1. 易证得)

      那就相当于点 j 把要分的的糖全部分给右边

    怎么抽象地证明呢:

      自己动手,丰衣足食...
      也不难,k 分给左边几个糖,分到 j 没了,说明 j 是有需求的

      相当于 j 带着需求跑到 k ,这时 j 的需求也刚好没了,然后 k 少了几个糖,把剩下的糖往另一边分

      也就相当于 j 带着全部需求跑了一圈,跑完就分完了

    所以由4.可知

    我们只需要判断每个点把(需求 or 盈余)只往一边分需要多少糖

    对于第一个点 k 要分b[ k ] 个,则对于 k+1 要分 b[k+1]+b[ k ],......,对于 k+m 则要分 b[ k,k+1,...,k+m] 个

    那要怎么快速计算这种东西呢

    前缀和...

    先预处理出 1 到 n 的前缀和,为sum

    对于前面的式子,可以化为 abs(sum[ k ]-sum[ k-1 ]),abs(sum[ k+1 ]-sum[ k-1 ]),...,abs(sum[ k+m ]-sum[ k-1 ])

    那要怎么选取 k-1 才能使之和最小呢

    稍微转换一下:

    把每个sum都放到数轴上,则前面的式子就是某个点 k-1 到所有其他点的距离之和

    怎么找最小的和那就是“货仓选址”问题

    好吧讲了这么多终于可以发代码了......

    代码难度为0.....

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    int n;
    int a[1000007],b[1000007];
    long long ans,tot;
    long long sum[1000007];
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read(),tot+=a[i];
        tot/=n;
    
        for(int i=1;i<=n;i++)
            b[i]=a[i]-tot;
    
        for(int i=1;i<=n;i++)
            sum[i]=sum[i-1]+b[i];
    
        sort(sum+1,sum+n+1);
        tot=sum[(n+1)/2];
    
        for(int i=1;i<=n;i++)
            ans+=abs(tot-sum[i]);
    
        cout<<ans;
        return 0;
    }
  • 相关阅读:
    堆排序算法的原理和实现
    图的深度优先搜索(DFS)和广度优先搜索(BFS)算法
    图的迪杰斯特拉算法求最短路径
    第13章 切换到混合流并添加API访问
    第12章 添加对外部认证的支持
    第11章 使用OpenID Connect添加用户身份验证
    第10章 使用密码保护API
    第9章 使用客户端凭据保护API
    第8章 概述
    第7章 贡献
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9537513.html
Copyright © 2011-2022 走看看