AOJ 1333 Beautiful Spacing

問題

n個の単語を、幅wのフィールドに、好きな行数にわけて書く。

  • 一つの単語は、間をあけずに、行をまたがずに書く。
  • 単語と単語の間は一つ以上のスペースを空けて書く
  • 行の先頭および末尾にはスペースを空けずに書く。
  • ただし、最後の1行の末尾はスペースを空けてよい。

このとき、単語と単語の間のスペースの最大値をなるべく小さくしたい。
その値を求めよ。

制約条件

n≦50000
w≦80000
単語は必ず一行に二つ以上書ける

方針

無理やり動的計画法で解いた。
普通は二分探索をするらしい。


単語をi個ちょうど使ったときの今までのスペースの最大値の最小値を、dp[i]とおく。
これを更新していくDPをするが、愚直に更新するとO(n^2).
(単語iからjを一行に入れるコストがO(1)で求まるとして)


これを高速化したい。
コストの関数に注目すると、
cost(i, j)は、jが増えると単調非増加になっている。
また、iが増えると単調非減少になっている。


dp[j] = min(dp[j], max(dp[i], cost(i, j))と更新されるので、
max(dp[i], cost(i, j)はあるところまでは非増加で、途中から横ばいになる。
iが1増えたとき、横ばいのところまでは、cost(i + 1, j) > cost(i, j)であるので、
見る必要がない。
つまり、前回の横ばいになる直前のところを覚えておくことで、
しゃくとり法で更新ができる。


横ばいのところは、別に更新しなくてはならない。
これは、範囲に対してminの更新ができればいいので、
遅延評価segment treeを使ってやればよい。

ソースコード

int w, n, x[50000], dp[50010];
ll s[50010];

inline int cost(int i, int j){
	assert(i + 1 < j);
	ll len = s[j] - s[i];
	if(len + j - i - 1 > w) return inf;
	return (w - len + j - i - 2) / (j - i - 1);
}
const int MX = 65536;
int dat[2 * MX], same[2 * MX];

inline int query(int a, int b, int k, int l, int r){
	if(b <= l || a >= r) return inf;
	if(a <= l && r <= b) return dat[k];
	if(same[k]){
		same[k] = 0;
		same[2 * k + 1] = same[2 * k + 2] = 1;
		dat[2 * k + 1] = min(dat[2 * k + 1], dat[k]);
		dat[2 * k + 2] = min(dat[2 * k + 2], dat[k]);
	}
	return min(query(a, b, k * 2 + 1, l, (l + r) / 2),
		query(a, b, k * 2 + 2, (l + r) / 2, r));
}
inline void fix(int a, int b, int k, int l, int r, int x){
	if(b <= l || a >= r) return;
	if(a <= l && r <= b){
		same[k] = 1;
		dat[k] = min(dat[k], x);
		return;
	}
	if(same[k]){
		same[k] = 0;
		same[2 * k + 1] = same[2 * k + 2] = 1;
		dat[2 * k + 1] = min(dat[2 * k + 1], dat[k]);
		dat[2 * k + 2] = min(dat[2 * k + 2], dat[k]);
	}
	fix(a, b, k * 2 + 1, l, (l + r) / 2, x);
	fix(a, b, k * 2 + 2, (l + r) / 2, r, x);
}
int main(){
	while(cin >> w >> n, w){
		rep(i, n){
			cin >> x[i];
			s[i + 1] = s[i] + x[i];
			dp[i + 1] = inf;
		}
		rep(i, MX * 2) dat[i] = inf, same[i] = 1;
		
		dp[0] = 1;
		int j = 0, ans = inf, co;
		rep(i, n){
			dp[i] = min(dp[i], query(i, i + 1, 0, 0, MX));
			
			if(i >= n - 1 || cost(i, n) < inf){
				ans = min(ans, dp[i]);
				continue;
			}
			while(j <= i + 1) j++;
			
			int lo = j - 1, hi = n + 1, mid;
			while(lo + 1 < hi){
				mid = (lo + hi) / 2;
				if(cost(i, mid) < inf) lo = mid;
				else hi = mid;
			}
			while(j <= n){
				if((co = cost(i, j)) >= inf) break;
				if(co < dp[i]) break;
				dp[j] = min(dp[j], max(dp[i], co));
				j++;
			}
			fix(j, hi, 0, 0, MX, dp[i]);
		}
		cout << ans << endl;
	}
	return 0;
}