TopCoder SRM 543 Div1 Medium EllysRivers

問題

xy平面上に、n本の川とn+1個の陸地がある。
i番目の川の幅はwidth[i]で、n番目の陸地とn+1番目の陸地の間を流れている。
その川を渡るときのスピードは、speed[i](方向によらず一定)である。


陸地は細長く、y軸に平行な線分とみなすものとする。
線分の下側の端点のy座標は0, 上側の端点のy座標はlengthである。
陸地の、座標が整数の点には船着場がある。


(0, 0)を出発して、陸地を歩くか、船を使い、n+1番目の陸地のy座標lengthの点へ移動したい。
陸地を歩くスピードはwalkである。船の出発点と到着点は船着場である必要がある。
移動にかかる最短時間を求めよ。

制約条件

n≦50
length≦100000
speed[i]≦100000
width[i]≦100000

方針

DP.
更新を全部やろうとするとTLEなので、計算量を落とす必要がある。


最小値を三分探索で見つけようとすると、ギリギリTLEするっぽい。
なので、しゃくとり的にやる。
dp[i][j] = min{dp[i-1][k] + dist[k - j]}を最小にするkは、
jを動かしたときに、常に右にずれていくので、
前のjから出発して右にdp[i-1][j] + dist[k - j]が減少する間kを増やしてやればいいことがわかる。


なんでdp[i-1[k] + dist[k - j]の最小値が右にずれていくかは、dp[i - 1][k]とdist[k - j]のグラフを考えたらわかる。
dp[i - 1][k]はkについて単調増加な関数。
dist[k - j]は、k = jのとこで最小値を取る下に凸な関数。
これが、jが増えると、distだけが右にずれるので、最小値を取るkは右にずれる。

ソースコード

double dp[60][100010];

class EllysRivers {
	public:
	double getMin(int length, int walk, vector <int> width, vector <int> speed) 
	{
		int n = width.size();
		rep(i, 60) rep(j, 100010) dp[i][j] = INF;
		dp[0][0] = 0;
		for(int k = 1; k <= n; k++){
			for(int i = 0, j = 0; i <= length; ){
				double a = dp[k - 1][j]     + hypot(width[k - 1], j - i )    / speed[k - 1];
				double b = dp[k - 1][j + 1] + hypot(width[k - 1], j + 1 - i) / speed[k - 1];
				if(a <= b) dp[k][i++] = a;
				else j++;
			}
		}
		double ans = INF;
		rep(i, length + 1) ans = min(ans, dp[n][i] + 1.0 * (length - i) / walk);
		return ans;
	}
};