P2512 [HAOI2008] 糖果传递 同 P5817 [CQOI2011] 分金币 同 P2125 图书馆书架上的书 同 P4016 负载平衡问题 题解

mygr

2023-10-25 17:49:45

Solution

逆天四倍经验 原题链接:[糖果传递](https://www.luogu.com.cn/problem/P2512),[分金币](https://www.luogu.com.cn/problem/P5817),[P2125 图书馆书架上的书](https://www.luogu.com.cn/problem/P2125),[P4016 负载平衡问题](https://www.luogu.com.cn/problem/P4016) 狠狠的AC~ 话虽如此,但我仍觉得这是值得一讲的好题 为了方便,接下来我们以“糖果传递”这一题为例 题意翻译应该不用了吧( 一眼看下去似乎无从下手啊,试着多从几个角度考虑 ### 动态规划: 如果当成线性dp的话,因为有环的出现而不好解决;区间dp角度的话,有点像石子合并那道题,但是不完全一样(完全不一样),而且时间复杂度也完全对不上,过 ### 网络流: 似乎可行,因为每个点的糖果数最后都是要回归平均值的,而用来补充**糖果数小于平均值的点**的糖果一定来自**糖果数大于平均值的点**,此时从大的点运输过来的代价就是两点间的距离,所以对于每个点向它的左右两个点连接一条容量为无限,费用为1的边,从源点向每个点连一条容量为糖果数,费用为0的边,从每个点向汇点连一条容量为平均值,费用为0的边,跑最小费用最大流即可 事实上,这就是[P4016 负载平衡问题](https://www.luogu.com.cn/problem/P4016)最原本的解法,不然你猜它为什么放在网络流24题里( 嗯,对了,就是有个小问题,时间复杂度:$O(nmf)$..... 怎么办,难道真的只能靠暴力搜索了吗 不急,我们来分析一下题目 对于每个点而言,它的糖果要么来自自己本身,要么来自它的左侧或右侧,左右侧同理,很容易想到作两个数组$l_i,r_i$,表示向左/右边提供多少个糖果,由于要存储的只是糖果的给拿关系,也可以用负数表示拿走多少个糖果,所以事实上我们只需要存储一个数组 $x_i$ 表示向右边提供/拿取多少个糖果,此时的答案为 $$\sum_{i=1}^{n}|x_i|$$ 设所有数的平均值为tot,此时很容易得出等式: $$a_i+x_{i-i}-x_i=tot$$ $$x_i=a_i+x_{i-1}-tot$$ 对于$i=1$而言,有 $$a_1+x_{n}-x_1=tot$$ 设$x_1=y$ 则有 $$x_2=a_2+y-tot$$ $$x_3=a_3+x_2-tot=y+a_3+a_2-2\times tot$$ $$x_i=y+ \sum_{j=1}^{i}a_j-i \times tot$$ 其中$\sum_{j=1}^{i}a_j-i \times tot$显然可以用前缀和维护 设$c_i=\sum_{j=1}^{i}a_j-i \times tot$ 则答案就转化成了 $$ans=\sum_{i=1}^{n}|x_i|=\sum_{i=1}^{n}|y+c_i|$$ 神奇! 此时问题就可以抽象为“在数轴上有n个点,求一个点使得它与这些点的距离的和最小” 可以证明当这个点在这些点的中位数时,答案最优,这里就不证明啦,这就叫做**中位数定理** ## Talk is cheap,show me the code ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int Max=2e5+5; int num[Max]; int n; int sum[Max]; int tot=0,ans=0; signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",&num[i]); tot+=num[i]; } tot/=n; for(int i=1;i<=n;i++) { sum[i]=sum[i-1]+tot-num[i]; } sort(sum+1,sum+1+n); for(int i=1;i<=n;i++) { ans+=abs(sum[(n+1)/2]-sum[i]); } printf("%lld",ans); } ```