首先看一下链式均分纸牌问题:
有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;
}