主要内容 #
- 问题描述
- 算法思路
- 参考程序
1. 问题描述 #
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如 N=4,4 堆纸牌数分别为: ① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取4张牌放到④(9 8 13 10)->从③取3张牌放到 ②(9 1110 10)-> 从②取1张牌放到①(10 10 10 10)。
输入格式
N(N 堆纸牌,1 <= N <=100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出格式
所有堆均达到相等时的最少移动次数。
样例输入
4
9 8 17 6
样例输出
3
2. 算法思路 #
以正常人的思维来想,肯定是从纸牌数最多的牌堆开始往旁边的牌堆移动纸牌,但是如果要程序中模拟这个过程无疑是比较困难的。因为是计算机处理的缘故,我们可以移动负数张纸牌,且最后达到的效果一样。
一开始先求出牌数的平均值,然后从第1堆开始遍历到第n-1堆牌,如果堆中的牌数不等于平均值,就移动堆中的牌数与平均值的差值张牌(这里无论正负)。接着,下一堆接收到移动过来的牌后,如果牌数不等于平均值,就移动差值张牌…如此循环反复,计算移动次数即可。
如果你能想到把每堆牌的张数减去平均张数,题目就变成移动正数,加到负数中去,使得所有的数字都变成0,那就意味着成功了一半。拿例题来说,平均张数为10,原张数为9,8,17,6,变为-1,-2,7,-4,其中没有为0的数,我们从左边出发,要使得第一堆的牌数-1变成0,只需要将-1张牌移到他的右边(第二堆)-2中;结果-1变成0,-2变成-3,各堆牌张数变成0,-3,7,-4;同理要使第二堆变成0,只需将-3堆移动到它的右边(第三堆)中去,各牌堆张数变为0,0,4,-4;要使第3堆变为0,只需要将第3堆中的4移到它的右边(第4堆)中去,结果为0,0,0,0,完成任务。每移动一次牌步数加1。其实从第i堆中移动-m张牌到i+1堆中,等价于从第i+1堆中移动m张牌到第i堆,步数是一样的。
3. 参考程序 #
#include<iostream> using namespace std; int main() { int i,j,n,step=0; //牌的堆数 cin>>n; int a[1000]; //每堆牌数 int ave=0; for(i=1;i<=n;i++) { cin>>a[i]; ave+=a[i]; } ave/=n; for(i=1;i<=n;i++) a[i]=ave-a[i]; //将每堆的牌变成与平均值差,此题关键 i=1;j=n; while(a[i]==0&&i<n) i++; //过滤左边(每次的最左边)的0 while(a[j]==0&&j>1) j--; //过滤右边(每次的最右边)的0 while(i<j) { a[i+1]+=a[i]; //将第i堆牌移到第i+1堆中去 a[i]=0; //第i堆牌移走后变为0 step++; //步数加1 i++; while(a[i]==0&&i<j) i++; //过滤移牌过程中产生的0 } cout<<step<<endl; return 0; }