こどふぉ特有の 回以下の操作で〜を達成せよ、という問題
問題概要
長さ の正の整数からなる数列 が与えられる。以下の操作を 回以下繰り返すことで、数列の値がすべて等しくなるようにしたい。そのような操作列を一つ求めよ。不可能である場合は -1 を出力せよ。
[操作]:整数 を選び、 の値を だけ減少させて、 の値を だけ増加させる。ただし の値が負になってはいけない
制約
考えたこと
まず、操作をどのように行っても、 の総和が不変であることに注目する。よって、明らかに の総和 が の倍数でなければならない。
このとき とする。すべての要素をうまいこと に揃えたい。色々迷走してしまったが、最終的にたどり着いたのは、次の方針だ。
- フェーズ I: 以外のすべての要素を 0 にする (それが可能であることを後に示す)
- フェーズ II: の要素を各要素に分配していって実現する
フェーズ I が実現可能ならば、フェーズ II は簡単だ。単に から各 に対して として操作を行なっていけばよい。
フェーズ I は、実は の順序で行なっていけばよいのだ。最悪ケースは
のように、ひたすら 1 が並ぶケース。しかし であることに注目しよう。そうすると、
であることが保証される。よって、一旦 から へ要素を移して の状態にしてから、 から に戻せば の状態になる。
より一般に、 の順に
- が の倍数になるように、 から適切な量を に配る
- が 0 になるように から へ配る
とすれば OK。これで最大でも 回の操作で実現できる。
コード
#include <bits/stdc++.h> using namespace std; void solve(vector<long long> &A) { int N = A.size(); long long sum = 0; for (auto v: A) sum += v; if (sum % N != 0) { cout << -1 << endl; return; } long long num = sum / N; // 0-indexed vector<long long> ri, rj, rx; auto run = [&](int i, int j, long long x) -> void { //cout << i+1 << " " << j+1 << " " << x << endl; if (x == 0) return; ri.push_back(i+1), rj.push_back(j+1), rx.push_back(x); A[i] -= x * (i+1); A[j] += x * (i+1); }; // 0 に集める for (int i = 1; i < N; ++i) { long long p = A[i] / (i+1), r = A[i] % (i+1); if (r == 0) run(i, 0, p); else { run(0, i, i+1-r); run(i, 0, A[i]/(i+1)); } } // 0 から揃えていく for (int i = 1; i < N; ++i) { long long dif = num - A[i]; run(0, i, dif); } // 出力 cout << ri.size() << endl; for (int i = 0; i < ri.size(); ++i) { cout << ri[i] << " " << rj[i] << " " << rx[i] << endl; } } int main() { cin.tie(0); ios::sync_with_stdio(false); int T; cin >> T; while (T--) { int N; cin >> N; vector<long long> A(N); for (int i = 0; i < N; ++i) cin >> A[i]; solve(A); } }