来源:Vijos 1123
均分纸牌
描述
有 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 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
格式
输入格式
N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出格式
所有堆均达到相等时的最少移动次数。
样例1
样例输入1
4
9 8 17 6
Copy
样例输出1
3
Copy
限制
每个测试点1s
考点:贪心
这个题直接做看起来并不是很好做,所以考虑先简化问题。首先注意到读入所有数据后可以直接求出平均数,再用每堆牌的初始数减去平均数,可以发现:
(以9 8 17 6为例)
原数组:9 8 17 6
新数组:-1 -2 7 -4
新数组中,每个数的含义变成这堆牌还需要(去掉)几张牌才能达到一样多时需要的牌数。-x代表需要x张牌,+x代表去掉x张牌。所以任务就变成了求最少的移动次数使得所有数都变为0。
思考发现,只用从左向右(或反向)遍历一次即可求出答案。这里贪心的思想是不能有任何多余的移动,就是说对于新数组中的任何数,只要它已经变为0,就不应该再对它进行任何操作。由于题目的限制条件,最左(右)的数只能和它的右(左)边传递牌数,也就是说,如果一开始就让最左(右)的数变为0,则接下来的其他数也将只能和某一边传递牌数,进行同样的操作,就能保证在没有任何多余的移动的情况下将所有数都变为0,而这样的操作仅需一次遍历就能够轻松完成。
例如:-1 -2 7 -4
变为:0 -3 7 -4
变为:0 0 4 -4
变为:0 0 0 0
一共进行了3次移动。
还要注意的地方是,这里给出的样例中出现了负数牌数的传递,这实际上就不是给出牌了,而是从别的牌堆获得牌,尽管有的时候这样的牌堆自身还需要牌,但这不过是操作顺序的问题,不会影响结果。
然后遍历过程中如果遍历到某个数时它本身就是0,就不计数,否则都计数一次,最终计数结果即为答案。
上代码:
#include <iostream>
using namespace std;
const int MAXN = 107;
int n, sum, cnt, A[MAXN];
int main()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> A[i], sum += A[i];
for (int i = 1; i <= n; ++i)
A[i] -= (sum / n);
for (int i = 1; i < n; ++i)
{
if (A[i]) ++cnt;
A[i + 1] += A[i];
}
cout << cnt << endl;
return 0;
}