SRM 504.5 550pt: TheJackpotDivOne

問題概要

J(<10^18)円持っている。N(<50)人メンバーが居てmoney[i](<10^18)円持っている。J円を次のように分配するとき最終的なメンバーの所持金をソートしたものを返す問題。

  • メンバーの所持金の平均をaとする。
  • 所持金が最小の者を一人選んで、所持金が平均より多くなるように金を渡す。足りなければ全額渡す。

解法

適当にシミュレーションしたら最大値と最小値の差が1以下になるので、そうなったら等分配すればよい。問題は平均値を取るところで、64bit整数だと愚直に和をとるとオーバーフローするし、整数との比較をする必要があるのでlong doubleにしても精度が足りなくて落ちる。なのでオーバーフローしないように工夫してやる。わざわざBigIntegerとか持ち出すまでもなく、以下のようにすれば回避できる。

int64 t = 0, avg = 0;
for(int i=0; i<N; i++){
    t += xs[i];
    avg += t/N;
    t %= N;
}
vector<long long> find(vector<long long> money, long long jackpot) {
	int N = money.size();
	while(jackpot>0){
		int64 mi = *min_element(money.begin(), money.end());
		int64 ma = *max_element(money.begin(), money.end());
		if(ma - mi <= 1){
			sort(money.begin(), money.end());
			for(int i=0; i<N; i++){
				if(money[i]==mi){
					money[i]++;
					jackpot--;
					if(jackpot==0){
						sort(money.begin(), money.end());
						return money;
					}
				}
			}
			int64 d = jackpot/N;
			for(int i=0; i<N; i++){
				money[i] += d;
			}
			jackpot -= d*N;
			sort(money.begin(), money.end());
			for(int i=0; i<N; i++){
				if(jackpot > 0){
					jackpot--;
					money[i]++;
				}
			}
			sort(money.begin(), money.end());
			return money;
		}
		int64 t = 0, avg = 0;
		for(int i=0; i<N; i++){
			t += money[i];
			avg += t/N;
			t %= N;
		}
		vector<long long >::iterator itr = min_element(money.begin(), money.end());
		int64 give = (avg+1) - *itr;
		if(give > jackpot){
			*itr += jackpot;
			jackpot = 0;
			break;
		}
		else{
			*itr += give;
			jackpot -= give;
		}
	}

	sort(money.begin(), money.end());
	return money;
}