线性均分纸牌
题目描述
有 n堆纸牌,编号分别为 1,2,…, n。每堆上有若干张,但纸牌总数必为 n 的倍数。可以在任一堆上取若于张纸牌,然后移动。移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 n 的堆上取的纸牌,只能移到编号为 n-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如 N=4,4 堆纸牌数分别为:(1)9 (2)8 (3)17 (4)6移动3次可达到目的:从(3)取4张牌放到(4)(9 8 13 10) -> 从 (3) 取 3 张牌放到 (2)(9 11 10 10)-> 从 (2) 取 1 张牌放到(1)(10 10 10 10)。
输入
n(n 堆纸牌,1 <= n <= 100)a1 a2 … an (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出
所有堆均达到相等时的最少移动次数。
思路
设牌的总数为cnt,平均数ave=cnt/n。
一、先考虑一个人,若a1>ave,则第一堆需要给第二堆a1-ave张牌,若a1<ave,则第一堆需要从第二堆拿ave-a1张纸牌。
二、按照同样的方法考虑第2-n堆,则可以得到最小移动次数为从1到n|i*ave-sum[i]|的和,sum[i]是a[i]的前缀和。
三、若一开始a[i]=a[i]-ave,即让每堆都减去ave,则最终每堆都有0张纸牌。所以最后当sum[i]=0的时候,当时的次数就是最小次数。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, ans = 0, ave, cnt = 0;
cin >> n;
int a[10000],sum[10000];
for(int i = 1; i <= n; i++)
{
cin >> a[i];
cnt += a[i];
}
ave = cnt / n;
sum[0] = 0;
for(int i = 1; i <= n; i++)
{
sum[i] = sum[i - 1] + a[i] - ave;
if(sum[i])
ans++;
}
cout << ans << endl;
return 0;
}
环性均分纸牌
题目描述
还是刚才的问题,只不过是刚才是线性的,现在变成了一个环; 即第一个人可以将自己的牌给最后一个人,也可以从最后一个人哪里拿牌; 最后一个人同理; 询问这个时候需要的最小代价。
思路
我们在做环形的题目时候有种经常使用的解法是将环形拆分为线形。此题当然也可以将环形拆分为线形。
一、有一个结论是 在均分纸牌的过程中,肯定有一个人是不参与的(因为当其他n - 1都处于均值是,则第n个人必定处于均值。
二、那我们可以枚举一下哪一项不进行参与,然后从这个点将环断开。然后O(n)扫出此时的最小代价, 找出一个最小代价即可。
三、优化。我们可以先对每一堆牌减去均值,然后求出一个前缀和。假如sum[i] = 0,则前i项都为平均值了。 所以我们最后只需要得到所有前缀和绝对值的和即可。
1、我们先来看看线性的均分纸牌(即把第n个人和第1个人之间把环断开),让我们对这个有个比较直观的认识;
假设 A数组存储的是减去均值后的纸牌个数。 S数组储存的是前缀和(减去均值的前缀和)
A[1] S[1]
A[2] S[2]
A[3] S[3]
… …
A[n] S[n]
我们可以显然得到,当某个 S[i]不等于0时,那代表我 前 i - 1项肯定不能自给自足,还需要第i给贡献或者拿走一部分; 所以我们求出这样的一个前缀和, 然后对这个前缀和数组进行求绝对值的和就是线性的最小代价; 要求最小移动次数就是 S[i]不为0 则需要移动;
2、我们假设我们的断开点是k点
那么减去均值和得到的前缀和数组就是
A[k + 1] S[k + 1] - S[k]
A[k + 2] S[k + 2] - S[k]
A[k + 3] S[k + 3] - S[k]
… …
A[n] S[n] - S[k]
A[1] S[1] + S[n] - S[k]
A[2] S[2] + S[n] - S[k]
… …
A[k] S[k] + S[n] - S[k]
考虑到最后一项,所以肯定有 S[k] + S[n] - S[k] = 0;
所以 S[n] = 0;
将式子中的S[n]替换为0,原式可以改写为:
A[k + 1] S[k + 1] - S[k]
A[k + 2] S[k + 2] - S[k]
A[k + 3] S[k + 3] - S[k]
… …
A[n] S[n] - S[k]
A[1] S[1] - S[k]
A[2] S[2] - S[k]
… …
A[k] S[k] - S[k]
显然,这题变成了, 求一个数组中的点, 使得到达其他所有点的距离和最小的问题;(显然是中位数) 。
我们选取一个前缀和的中位数, 然后求一下当前点到其他点绝对值的和就是答案啦;
#include <bits/stdc++.h>
using namespace std;
#define MAX_N 1000010
int a[MAX_N], sum[MAX_N];
int main()
{
int n;
cin >> n;
sum[0] = 0;
long cnt = 0;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
cnt += a[i];
}
int age = cnt / n;
for(int i = 1; i <= n; i++)
{
a[i] -= age;
sum[i] = sum[i - 1] + a[i];
}
sort(sum + 1, sum + 1 + n);
int mid = (n + 1) >> 1;
long long ans = 0;
for(int i = 1; i <= n; i++)
{
ans += abs(sum[i] - sum[mid]);
}
cout << ans << endl;
return 0;
}