題目連結:洛谷P2339 [USACO04OPEN] Turning in Homework G
首先我們考慮如何處理到達給定時間後才能交作業這一限制。其實在生活中,我們一般只會考慮什麼時候交作業截止 (除了某些卷王),這樣我們只用考慮如何在最大結束時間之前交作業,而不是在所有作業都沒開始交之前考慮如何轉悠(前者明顯比後者簡單),這樣我們可以先二分答案貝茜交齊所有作業的總時間,再來判斷能否在這時間內從原來的終點交齊作業並最終來到原來的起點。
現在考慮如何判斷是否可以在給定條件下交齊所有作業。可以知道如果有三份要交的作業 \(A, B, C\),他們距離當前點的距離 \(x_A < x_B < x_C\),由圖:
可得從起始點先到 \(A\) 再到 \(B\) 最後到 \(C\) 一定優於從 先從起點到 \(B\) 再到 \(A\) 最後到 \(C\),因此,如果貝茜要在這條走廊上來回交作業,那麼她每次改變方向後所走的區間長度總小於上一次,而不是先走一個大區間,再走一個小區間,然後又走一個大區間。反過來,這就是一個從小區間推到大區間的過程,可以用區間 DP 做了。
考慮設 \(dp_{l, r, 0/1}\) 表示交完 \(l\) 到 \(r\) 區間內的作業,最後停留在左端點 \(l\) 的最小時間為 \(dp_{l, r, 0}\),停留在右端點 \(r\) 的最小時間為 \(dp_{l, r, 1}\),下面分四種情況討論:
-
現在在某個區間的左端點,要往左繼續交作業。
-
現在在某個區間的右端點,要往左繼續交作業。
-
現在在某個區間的左端點,要往右繼續交作業。
-
現在在某個區間的右端點,要往右繼續交作業。
每種情況只用考慮到達時交作業是否截止,如果沒有截止就可以更新答案,因此 DP 方程為:
注意:
-
將時間倒序後,記得將交作業截止時間更新為總時間減去原先開始交作業的時間。
-
記得將出發點與結束點也存為一個辦公室,方便統計答案。
完整程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
struct Homework{
int x, t;
} h[N];
bool cmp(Homework a, Homework b){
if(a.x == b.x)
return a.t < b.t;
return a.x < b.x;
}
int C, H, B;
int tmp[N];
int dp[N][N][2];
bool chk(int tim){
for(int i = 1; i <= C; i++){
tmp[i] = tim - h[i].t;
if(h[i].t > tim)
return 0;
}
memset(dp, 0x3f, sizeof(dp));
dp[B][B][0] = dp[B][B][1] = 0;
for(int len = 2; len <= C; len++)
for(int l = 1; l + len - 1 <= C; l++){
int r = l + len - 1;
if(dp[l + 1][r][0] + h[l + 1].x - h[l].x <= tmp[l])
dp[l][r][0] = min(dp[l][r][0], dp[l + 1][r][0] + h[l + 1].x - h[l].x);
if(dp[l + 1][r][1] + h[r].x - h[l].x <= tmp[l])
dp[l][r][0] = min(dp[l][r][0], dp[l + 1][r][1] + h[r].x - h[l].x);
if(dp[l][r - 1][1] + h[r].x - h[r - 1].x <= tmp[r])
dp[l][r][1] = min(dp[l][r][1], dp[l][r - 1][1] + h[r].x - h[r - 1].x);
if(dp[l][r - 1][0] + h[r].x - h[l].x <= tmp[r])
dp[l][r][1] = min(dp[l][r][1], dp[l][r - 1][0] + h[r].x - h[l].x);
}
if(dp[1][C][0] != 0x3f3f3f3f)
return 1;
else
return 0;
}
int main(){
scanf("%d%d%d", &C, &H, &B);
for(int i = 1; i <= C; i++)
scanf("%d%d", &h[i].x, &h[i].t);
h[++C] = Homework{B, 0};
++C;
sort(h + 1, h + C + 1, cmp);
for(int i = 1; i <= C; i++)
if(h[i].x == B && h[i].t == 0){
B = i;
break;
}
int l = 1, r = N * N;
while(l <= r){
int mid = (l + r) >> 1;
if(chk(mid))
r = mid - 1;
else
l = mid + 1;
}
printf("%d", r + 1);
return 0;
}