糖果传递(环形均分纸牌问题)

首先看一下链式均分纸牌问题
有N堆纸牌,编号分别为 1,2,…,N。
每堆上有若干张,但纸牌总数必为 N 的倍数。
可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为 1 的堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N−1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
(思路都在代码里)

const int N = 110;
int n,a[N];

int main()
{
    
    
	cin>>n;
	int tot=0; //总数
	for(int i=1;i<=n;i++)	cin>>a[i], tot+=a[i]; 
	int aver=tot/n,step=0; //算出每一堆应该有多少张牌
	for(int i=1;i<n;i++) //从第一张开始向后推
	{
    
    
		if(a[i]!=aver) //如果当前堆不满足,将该堆多的(或少的)扔给下一堆处理
		{
    
    
			step++;
			a[i+1]+=a[i]-aver; //传给下一堆
			a[i]=aver;
		}
	}
	cout<<step<<endl;
	return 0;
}

再来看环形均分纸牌问题
有n个小朋友坐成一圈,每人有a[i]个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。
求使所有人获得均等糖果的最小代价。

输入格式
第一行输入一个正整数n,表示小朋友的个数。
接下来n行,每行一个整数a[i],表示第i个小朋友初始得到的糖果的颗数。

输出格式
输出一个整数,表示最小代价。


做法:
首先将模型抽象出来,假设第i个小朋友给了第i-1个小朋友X(i)个,第i+1个小朋友给了第i个小朋友X(i+1)个,可以画出这样一个图:
(x可正可负,x如果是负的代表是反过来给)在这里插入图片描述
我们的目的是求: |x1| + |x2| + …… + |xn-1| + |xn| 的和最小。
经过这样一番传递之后,第i个人的最终糖果为 ai-X(i)+X(i+1)
又因为每个人最后的糖果数量一样,都为平均值aver=(a1+a2+……+an)/n;
所以 ai-X(i)+X(i+1) = aver ,将所有人的等式列出来可以得到这样一组方程:
在这里插入图片描述
左右移项有:
在这里插入图片描述

仔细观察发现:
我们可以把每一个Xi用X1表示
(由低至上递推)
在这里插入图片描述
来看等式右边,除了X1不确定,其他的都是确定不变的量。然而好像看不出什么东西来
我们把式子做一下变形:
在这里插入图片描述

变成相减的形式更有利于我们转化问题 --> 在数轴上,两个数相减是有它的几何意义的:两个数相减得到的绝对值是这两个点之间的距离。正好我们要求的也是绝对值
这样问题就变成了给你n个点,在这n个点中找出一个点,使得该点到其他所有点的距离之和最小,这个点的值就是式子中x1的值
画一画便可以知道,这个点是所有点中间的那个点(如果有奇数个点,就是最中间的点;偶数点,就是中间两个点的任意一个)
(这n个点是在这里插入图片描述

#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstring>
#include<iostream>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<vector>
#define ll long long
#define ull unsigned long long
#define up_b upper_bound
#define low_b lower_bound
#define m_p make_pair
#define mem(a) memset(a,0,sizeof(a))
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define inf 0x3f3f3f3f
#define endl "\n"
#include<algorithm>
using namespace std;

inline ll read()
{
    
    
	ll x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9')	{
    
     if(ch=='-') f=-1; ch=getchar(); }
	while('0'<=ch&&ch<='9')	x=x*10+ch-'0', ch=getchar();
	return f*x;
}

const int N = 1e6+5;

int n,a[N],f[N];

int main()
{
    
    
	cin>>n;
	ll aver,tot=0; //总数 
	for(int i=1;i<=n;i++)	a[i]=read(), tot+=a[i];
	aver=tot/n; //平均值,最终每个人手中的糖果数 
	
	//算出那n个点
	for(int i=n;i;i--)	f[i]=f[i+1]+aver-a[i];
	sort(f+1,f+1+n); //一定要将所有点从小到大排序
	 
	int mid=n+1>>1; //找到中点,上取整 
	ll res=0;
	for(int i=1;i<=n;i++)	res+=abs(f[i]-f[mid]); //取绝对值 
	cout<<res<<endl;
	return 0; 
}

猜你喜欢

转载自blog.csdn.net/m0_50815157/article/details/113808110
今日推荐