題目大意
場景中包括多個長度和高度各不相同的平臺。地面是最低的平臺,高度爲零,長度無限。
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條件的東西,那麼我們不妨從底向上分析。令
- 表示第k個平臺從左側下落到底部的最少用時
- 表示第k個平臺從右側下落到底部的最少用時
爲很麼要分成這兩部分呢?讀者可以思考一下
假設我們有個平臺,高度從高到低排列,此時我們知道了後個平臺每個下降到底部的最少用時,那麼如何確定第的最小用時呢?
我們不妨遍歷中間的所有平臺,如果從的左側下落能到達平臺,那麼我們要進一步決定是從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;
}
}