Yurchiu's Blog

P2512 [HAOI2008] 糖果传递

Yurchiu 2021-07-28, 20:51:43 1.5k 隐藏左右两栏 展示左右两栏

因为昨天模拟赛考了这个题,并且爆 0 了,所以应当为此题写一篇题解。

题意

nn 个小朋友坐成一圈,每人有 aia_i 个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为 11。求使所有人获得均等糖果的最小代价。n106n\le10^6

点击查看题解

题解

思路 1

环形均分纸牌P1031 [NOIP2002 提高组] 均分纸牌

一般的均分纸牌问题就相当于在第 nn 个人与第 11 个人之间把环断开,此时这 nn 个人站成一行,第 ii 人持有的纸牌数、前缀和分别是 aia_isis_i。设 ansans 为答案。设 xx 为平均数。不在上面加横杠了,因为懒

如果在第 kk 个人之后把环断开站成一行,则:

  • i[k+1,n]\forall i\in[k+1,n],第 ii 个人持有的数目的前缀和是 sisks_{i}-s_k
  • i[1,k]\forall i\in[1,k],第 ii 个人持有的数目的前缀和是 si+snsks_i+s_{n}-s_k

我们考虑整体和隔离的思想。回到一般的均分纸牌问题,将前 ii 个看做一个整体,显然前 ii 个内部的均分是不会改变其整体结构的,因而对于该体系来说,想要达到平均结构,就必须与下一个体系交换足够的纸牌,而交换数量就是 siix|s_i - ix|,然后就可以推出一个结论:

ans=i=1nsiixans=\sum\limits_{i=1}^n |s_i-ix|

也就是将每次体系更新的贡献加起来。

若设 ai=aixa_i'=a_i-xsis_i'aia_i' 的前缀和,则:

si=siixs_i'=s_i-ix

结合上面的式子,得:

ans=i=1nsians=\sum\limits_{i=1}^n |s_i'|

这就是均分纸牌问题的通用公式。

对于环形问题,首先考虑切开。因为 si=siixs_i'=s_i-ix,所以 sn=0s_n'=0。所以 sis_i' 前缀和的变化仅仅是减去 sks_k'。所以,所需最小花费为:

i=1nsisk\sum\limits_{i=1}^{n}|s_i'-s_k'|

那么,现在是要找到一个 sks_k',使得上面的式子的值最小。显然,sks_k' 就是 ss' 数列的中位数(由于思路 2 也要用到这个结论,所以证明在思路 2)。

所以就做完了。找第 kk 大的数存在 O(n)O(n) 算法,所以时间复杂度为 O(n)O(n)

思路 2

显然,要使每个人获得均等糖果,每个人的糖果数应为 xx。设第 ii 个人需向右传 bib_i 个糖果,对于第 ii 个人易得式子:

{ai+bi1bi=xi[2,n]ai+bnbi=xi=1\begin{cases} a_i+b_{i-1}-b_i=x & i\in[2,n] \\\\ a_i+b_n-b_i=x & i=1 \end{cases}

变形得:

bi={ai+bi1xi[2,n]ai+bnxi=1b_i=\begin{cases} a_i+b_{i-1}-x & i\in[2,n] \\\\ a_i+b_n-x & i=1 \end{cases}

i[2,n]\forall i\in[2,n],展开递推式,得:

bi=b1+j=2i(ajx)b_i=b_1+\sum\limits_{j=2}^i(a_j-x)

bi=b1+sis1x(i1)b_i=b_1+s_i-s_1-x(i-1)

ci=s1si+x(i1)c_i=s_1-s_i+x(i-1),则得:

bi=b1cib_i=b_1-c_i

题目要求最小化:

i=1nbi\sum\limits_{i=1}^n |b_i|

即:

i=1nb1ci\sum\limits_{i=1}^n |b_1-c_i|

那么,至少预处理所有 cic_i 是可以 O(n)O(n) 的。现在是要找到一个 b1b_1,使得上面的式子的值最小。显然,b1b_1 就是 cc 数列的中位数。

证明:把每个 cic_i 对应到数轴上。设 i[1,n],j=ni+1\forall i\in[1,n],j=n-i+1。对 cic_icjc_j 两两配对。那么对于每个数对 (ci,cj)(c_i,c_j),两者距离最小值一定是 cicj|c_i-c_j|(两点之间线段最短)。则在数轴上, b1b_1 可以选择在 cic_icjc_j 中间。这样,考虑每个数对,最后易得 b1b_1 就是 cc 数列的中位数。由于 cicj=cib1+b1cj|c_i-c_j|=|c_i-b_1|+|b_1-c_j|,易得其正确性。

所以就做完了。找第 kk 大的数存在 O(n)O(n) 算法,所以时间复杂度为 O(n)O(n)

代码

#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
	typedef long long ll;
	const ll N=1000000+10;
	ll n,a[N],s[N],c[N],x,mid,ans=0;
	void solve1()
	{
		for(ll i=1;i<=n;i++) a[i]-=x;
		for(ll i=1;i<=n;i++) s[i]=s[i-1]+a[i];
		nth_element(s+1,s+mid,s+1+n);
		//nth_element(i,j,k,l)
		//i,j,k 都是迭代器。在 [i,k) 区间内进行重排,
		//使得比 j 大的和比 j 小的以 j 为分界分开。
		//此时第 j 大的元素刚好是第 j 个元素。 
		//l 是自定义函数,判断大小。 
		for(ll i=1;i<=n;i++)
			ans+=abs(s[i]-s[mid]);
	}
	void solve2()
	{
		for(ll i=1;i<=n;i++) s[i]=s[i-1]+a[i];
		for(ll i=1;i<=n;i++) c[i]=s[1]-s[i]+x*(i-1);
		nth_element(c+1,c+mid,c+1+n);
		for(ll i=1;i<=n;i++)
			ans+=abs(c[i]-c[mid]);
	}
	void yzmain()
	{
		scanf("%lld",&n);
		for(ll i=1;i<=n;i++)
		{
			scanf("%lld",a+i);
			x+=a[i];
		}
		x/=n;mid=(1+n)/2;
		solve2();
		printf("%lld\n",ans);
		return;
	}
}
int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	_yz::yzmain();
	return 0;
}




本文作者:Yurchiu

本文链接:https://yz-hs.github.io/1a06ceae98f4/

版权声明:本博客中所有原创文章除特别声明外,均允许规范转载,转载请注明出处。所有非原创文章,按照原作者要求转载。


By Yurchiu.
其他物件杂物收纳
Hitokoto

Yurchiu 说,除了她以外的人都很强!嘤嘤嘤~~
博客信息
文章数目
158
最近更新
08-21
本站字数
350.6k
文章目录