CODE FESTIVAL 2015 あさプロ Middle C - 一次元オセロ

解法

最後まで残す白石を全通り調べる。書きやすいから石を縦に並べて説明する。

区間を重みA[i]を持つ一つの石として考える。最後まで残す石を◎に決めると、それぞれの石を裏返す回数は以下のとおり。
○… 6回
●… 5回
○… 4回
●… 3回
○… 2回
●… 1回

●… 1回
○… 2回
●… 3回
○… 4回
(実際には操作の度に1枚ずつ石が増えていくけど、適当に計算できるので意識しない。)

このとき掛かるコストは
{(6A_1+5A_2+4A_3+3A_4+2A_5+A_6)+(A_8+2A_9+3A_{10}+4A_{11}) }

白石を決めるたびにこれを計算すると間に合わないので、結果を再利用できないか考えてみる。最終的に黒を残すことはないけど、わかりやすいから隣りの黒を残す。
○… 7回
●… 6回
○… 5回
●… 4回
○… 3回
●… 2回
○… 1回

○… 1回
●… 2回
○… 3回

このとき掛かるコストは
{(7A_1+6A_2+5A_3+4A_4+3A_5+2A_6+A_7)+(A_9+2A_{10}+3A_{11}) }
である。

先ほどの左側のコストを{L}、右側のコストを{R}とすると、
{L'=L+(A_1+A_2+A_3+A_4+A_5+A_6+A_7)}
{R'=R-(A_8+A_9+A_{10}+A_{11})}
になっている。差分は区間の総和なので累積和を計算しておけば高速に計算できる。

#include <bits/stdc++.h>
#define GET_MACRO(a, b, c, NAME, ...) NAME
#define rep(...) GET_MACRO(__VA_ARGS__, rep3, rep2)(__VA_ARGS__)
#define rep2(i, a) rep3 (i, 0, a)
#define rep3(i, a, b) for (int i = (a); i < (b); i++)
#define repr(...) GET_MACRO(__VA_ARGS__, repr3, repr2)(__VA_ARGS__)
#define repr2(i, a) repr3 (i, 0, a)
#define repr3(i, a, b) for (int i = (b) - 1; i >= (a); i--)
#define chmin(a, b) ((b) < a && (a = (b), true))
#define chmax(a, b) (a < (b) && (a = (b), true))
using namespace std;
typedef long long ll;
 
int main() {
	int n;
	cin >> n;
	vector<ll> a(n);
	rep (i, n) scanf("%lld", &a[i]);
	vector<ll> sum(n + 1);
	rep (i, n) sum[i + 1] = sum[i] + a[i];
 
	ll ans = 2e18, L = 0, R = 0;
	rep (i, n) R += a[i] * i;
	rep (i, n) {
		int j = n - i - 1;
		ll ul = (ll)i * (i - 1) / 2;
		ll ur = (ll)j * (j - 1) / 2;
		ll cand = L + R + ul + ur;
		if ((i + n) % 2 == 1) {
			chmin(ans, cand);
		}
		L += sum[i + 1];
		R -= sum[n] - sum[i + 1];
	}
	cout << ans << endl;
	return 0;
}

感想

i*(i-1)/2でオーバーフローやらかした。