1.题意:给你n堆石头,每堆有a[i]个石头,每次可以从一堆移动一颗石头到另一堆,问最小的移动次数使存在x让所有a[i]%x ==0 成立。即最后的每堆石头都有一个共同的公因数(x>1)
2.分析:
(1)因为石头的总数在移动中是不变的,所以如果最后 a[0]%x==0 && a[1]%x==0 && a[2]%x==0 && ......&&a[n]%x==0
则有: a[0]%x + a[1]%x + a[x]%x + ....... + a[n]%x == 0
由取余运算知:(a[0] + a[1] + a[2] + ..... + a[n])%x==0
也就是说:这个因数x一定是 石子和 sum 的一个因数。
(2)由以上知:我们可以求出sum所有的质因数(非质因数也一定包含质因数,只需求质因数即可),枚举所有质因数下的最小移动次数即可。
(3)怎么判断移动次数呢?在已知x情况下,每堆石头距离成为x的倍数,最少差b[i] = a[i]%x个石头,这就是这堆石头需要补的石子数目。把所有堆石头需要补的数目求出来(不一定是补,还有可能是拿走),如果b[i]很小,我们拿到余数为0是这堆移动最少的。如果b[i]很大,我们补到x是最少的。那我们先补大的还是先拿小的呢?如果我们拿走了小的,那么小的放在哪呢?所以我们应该先补大的,补的数目就来自于前方小的,哪一堆?不用关心
(4)我们需要移动改变的总石子数ans = (b[0] + b[1] + b[2] + .... + b[n]),可以证明ans%x==0
如果我们补好了一个大的,那么这需要修改的石子总数就减少了x,移动次数 =x - b[i]
如果ans到了0,那么就说明所有石子都移动好了,当前操作次数就是总次数
3.代码:
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 1e18//因为LL,所以最大要设置LL最大
const int maxn = 100000 + 7;
typedef long long LL;
LL stone[maxn],fac[maxn],len;
LL num[maxn];
LL sum;
int n;
void Reslove(LL ans){//分解质因数
for(LL i = 2;i*i<=ans;i++){
if(ans&&ans%i==0){
fac[len++] = i;//保存
while(ans%i==0&&ans){//把因数除尽
ans/=i;
}
}
}
if(ans>1)fac[len++] = ans;//最后剩下一个质因数
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
sum = len = 0;
scanf("%d",&n);
for(int i = 0;i<n;i++){
scanf("%lld",&stone[i]);
sum+=stone[i];
}
Reslove(sum);//分解和的质因数
LL minn = INF;
for(int j = 0;j<len;j++){//枚举所有因数
LL ans = 0;
for(int i = 0;i<n;i++){
num[i] = stone[i]%fac[j];//每堆多余石子
ans+=num[i];
}
sort(num,num+n);//排序
int k = n-1;
LL moved = 0;
while(ans>0&&k>=0){//先补大的
moved+=(fac[j] - num[k]);//移动次数
ans-=fac[j];//减去已经操作好了的石子数目
k--;
}
minn = min(minn,moved);
}
printf("%lld\n",minn);
}
return 0;
}