POJ 1661 Help Jimmy【題解報告|DFS|DP】

題目大意

在這裏插入圖片描述
場景中包括多個長度和高度各不相同的平臺。地面是最低的平臺,高度爲零,長度無限。

Jimmy老鼠在時刻0從高於所有平臺的某處開始下落,它的下落速度始終爲1米/秒。當Jimmy落到某個平臺上時,遊戲者選擇讓它向左還是向右跑,它跑動的速度也是1米/秒。當Jimmy跑到平臺的邊緣時,開始繼續下落。Jimmy每次下落的高度不能超過MAX米,不然就會摔死,遊戲也會結束。

設計一個程序,計算Jimmy到底地面時可能的最早時間。

DFS

這道題,爆搜顯然是能找到正確答案的,我們從上往下跳,落到一個平臺上的時候,只有兩種選擇,向左跑和向右跑,只要依次dfs這兩種選擇即可,但是數據量太大,會T。

#define inf 0x3f3f3f3f
#define ll long long
#define vec vector<int>
#define P pair<int,int>
#define MAX 1005

struct plat {
	int x1, x2, h;
}ps[MAX];
bool cmp(plat p1, plat p2) { return p1.h > p2.h; }

int T, N, X, Y, M, res;

//當前遍歷到第k個平臺,他下墜的時候在x位置,y高度,一共用了t時間
void dfs(int k, int x, int y, int t) {
	if (k == N) {//墜落到地面
		if (y <= M && t + y < res)res = t + y;
		return;
	}
	if (t + y > res || y - ps[k].h > M)//掉到這裏一定摔死
		return;//剪枝1:最少時間都超過結果值
	
	if (x >= ps[k].x1&&x <= ps[k].x2) {//可以掉到這個平臺上
		dfs(k + 1, ps[k].x1, ps[k].h, t + x - ps[k].x1 + y - ps[k].h);//往左邊走
		dfs(k + 1, ps[k].x2, ps[k].h, t + ps[k].x2 - x + y - ps[k].h);//往右邊走
	}
	else dfs(k + 1, x, y, t);
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d %d", &N, &X, &Y, &M);
		for (int i = 0; i < N; i++)
			scanf("%d %d %d", &ps[i].x1, &ps[i].x2, &ps[i].h);
		sort(ps, ps + N, cmp);
		ps[N].x1 = -20000, ps[N].x2 = 20000, ps[N].h = 0;//最後一個平臺是地面

		res = inf;
		dfs(0, X, Y, 0);
		cout << res << endl;
	}
}

DP

既然直接爆搜不可以,我們不妨思考一下如何進行簡化,可以剪枝嘛?或者可以記憶化搜索嘛?對剪枝而言,上述程序已經用了一步,我暫時也沒有想到很好的剪枝策略,而且數據範圍這麼大,應該很難剪到規定時限內吧。排除掉剪枝這個方法,唯一的優化策略就剩下了能不能將他轉化爲記憶化搜索/DP。

從頂向下飛,我們好像看不出來任何符合DP條件的東西,那麼我們不妨從底向上分析。令

  • dpl[k]dpl[k]表示第k個平臺從左側下落到底部的最少用時
  • dpr[k]dpr[k]表示第k個平臺從右側下落到底部的最少用時

爲很麼要分成這兩部分呢?讀者可以思考一下

假設我們有nn個平臺,高度從高到低排列,此時我們知道了後kk個平臺每個下降到底部的最少用時,那麼如何確定第k1k-1的最小用時呢?

我們不妨遍歷j=k:nj=k:n中間的所有平臺,如果從k1k-1的左側下落能到達平臺jj,那麼我們要進一步決定是從j的左側下去,還是從j的右側下去,從二者之中取一個最小的。這也就是爲什麼我需要兩個dp數組。

在找到了所有平臺左右兩端下降到最低點的最小用時之後,我們就可以從起始點開始,找到他能夠碰到的平臺,取用時最少的操作即可。

不要忘了直接跳到地面的情況

//184K	0MS
#define inf 0x3f3f3f3f
#define ll long long
#define vec vector<int>
#define P pair<int,int>
#define MAX 1005

struct plat {
	int x1, x2, h;
}ps[MAX];
bool cmp(plat p1, plat p2) { return p1.h > p2.h; }

int T, N, X, Y, M, dpl[MAX], dpr[MAX];

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d %d", &N, &X, &Y, &M);
		for (int i = 0; i < N; i++)
			scanf("%d %d %d", &ps[i].x1, &ps[i].x2, &ps[i].h);
		sort(ps, ps + N, cmp);
		ps[N].x1 = -200000, ps[N].x2 = 200000, ps[N].h = 0;//最後一個平臺是地面

		
		fill(dpl, dpl + MAX, inf);
		fill(dpr, dpr + MAX, inf);
		dpl[N] = 0; dpr[N] = 0;
		for (int i = N - 1; i >= 0; i--) {
			plat p = ps[i], t;
			bool ls = 1, rs = 1;
			for (int j = i + 1; j <= N && p.h - ps[j].h <= M && (ls || rs); j++) {
				t = ps[j];
				//p可以從左邊降落到t
				if (ls && p.x1 >= t.x1&&p.x1 <= t.x2) {
					//到這裏後向左走下平臺或者向右走下平臺的最短的
					int mi = min(p.x1 - t.x1 + dpl[j], t.x2 - p.x1 + dpr[j]);
					if (j == N)mi = t.h;
					if (mi + p.h - t.h < dpl[i])
						dpl[i] = mi + p.h - t.h;
					ls = 0;//她從左邊只能降落到離他最近的這個
				}
				//p可以從右邊降落到t
				if (rs && p.x2 >= t.x1&&p.x2 <= t.x2) {
					int mi = min(p.x2 - t.x1 + dpl[j], t.x2 - p.x2 + dpr[j]);
					if (j == N)mi = t.h;
					if (mi + p.h - t.h < dpr[i])
						dpr[i] = mi + p.h - t.h;
					rs = 0;
				}
			}
		}

		int res = inf;
		for (int i = 0; i <= N && Y - ps[i].h <= M; i++) {
			plat p = ps[i];
			if (X >= p.x1&&X <= p.x2) {
				int t = min(dpl[i] + X - p.x1, dpr[i] + p.x2 - X);
				if (i == N)t = 0;//需要考慮直接跳下來的情況
				if (t + Y - p.h < res)
					res = t + Y - p.h;
				break;
			}
		}
		cout << res << endl;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章