Codeforces 321(#190 Div1) E. Ciel and Gondolas

問題

n人の人が一列に並んでいて、k個のゴンドラにわけて乗せる。
一つのゴンドラには何人でも乗れるが、乗せる順番は並び順でなければならない。


人同士には仲の悪さが決まっていて、i番目とj番目の人の仲の悪さはa[i][j] (=a[j][i]).
一つのゴンドラ内の不幸せさは、ゴンドラ内の全ての二人i, jについてΣa[i][j].
乗せ方を最適にしたとき、全てのゴンドラの不幸せさの総和の最小値を求めよ。

制約条件

n≦4000, k≦800
0≦a[i][j]≦9
a[i][j] = a[j][i], a[i][i] = 0

方針

Monge系のDP.
dp[i][j] := i個のグループに分けて先頭j人を乗せるときの最適解
としたとき、これを更新するDPは、時間計算量がO(kn^2).


この問題だとA[i][j] = argmin_k{dp[i][j] = dp[i-1][k] + cost[k][j]}としたとき、
A[i][j]≦A[i][j + 1]が成り立つらしい。証明まだ理解してないので後で理解したら書く。
参考→http://www.ics.uci.edu/~eppstein/pubs/EppGalGia-FOCS-88.pdf


とりあえずコストの関数cost(i, j)がMongeなのは、
Monge⇔cost(i, l) + cost(j, k) ≧ cost(i, k) + cost(j, l)が成り立っていること。
この場合cost(i, l) + cost(j, k) = cost(i, k) + cost(j, l) + 2 * cost(k, l)
が成り立っていて、cost(x, y)≧0だから。


なので分割統治法で、
rec(l, r, kl, kr)でdp[g][l]からdp[g][r]を計算する関数として、
m = (l + r) / 2に対してdp[g][m]を最適にするkを求めて、
rec(l, m, kl, k + 1)
rec(m, r, k + 1, r)を再帰的に呼べば計算量がO(knlogn)に減る。


入力が1600万もあるのでscanfすると死ぬ。
文字列として一行ずつ読んで自力でパーズしないとダメ。

ソースコード

const int MX = 4010;
int n, g, a[MX][MX], cost[MX][MX];
int dp[MX][MX];
char buf[2 * MX];

inline void rec(int g, int l, int r, int kl, int kr){
	if(kl + 1 == kr){
		for(int i = l; i < r; i++) dp[g][i] = dp[g - 1][kl] + cost[kl][i];
		return;
	}
	int m = (l + r) / 2, k;
	dp[g][m] = inf;
	for(int i = kl; i < kr && i < m; i++) if(dp[g][m] > dp[g - 1][i] + cost[i][m]){
		k = i;
		dp[g][m] = dp[g - 1][i] + cost[i][m];
	}
	if(l + 1 == r) return;
	
	rec(g, l, m, kl, k + 1);
	rec(g, m, r, k, kr);
}

int main(){
	scanf("%d%d", &n, &g);
	gets(buf);
	rep(i, n){
		gets(buf);
		rep(j, n) a[i][j + 1] += a[i][j] + buf[2 * j] - '0';
	}
	rep(i, n) for(int j = i + 1; j <= n; j++) cost[i][j] = cost[i][j - 1] + a[j - 1][j] - a[j - 1][i];
	
	rep(i, n + 1) dp[1][i] = cost[0][i];
	rep(i, g - 1) rec(i + 2, 0, n + 1, 0, n);
	cout << dp[g][n] << endl;
	
	return 0;
}