NOIP某年的初賽題,嗯...
題面
在一個月黑風高的夜晚,有N個人要過橋,他們只有一盞燈,橋很窄,每次最多只能允許兩個人過橋,現在告訴你每個人過橋需要的時間,問你最少需要多少時間能使得所有的人都過橋。
(如果兩個人的過橋時間不同,則他們一起過橋的時間按照過橋慢的人的時間算。)
輸入格式
第一行輸入一個整數N
第二行輸入N個整數,表示N個人過橋需要的時間
輸出格式
輸出一行,包含一個整數,表示所有人過橋需要的最少時間
約定
1 ≤ 所有的數 ≤ 1000
樣例輸入
5 1 3 6 8 12
樣例輸出
29
樣例解釋
1 3一起過橋,花費時間3
3回來,花費時間3
8 12一起過橋,花費時間12
1回來,花費時間1
1 6一起過橋,花費時間6
1 回來,花費時間1
1 3一起過橋,花費時間3
一共花費3 + 3 + 12 + 1 + 6 + 1 + 3 = 29分鍾
分析
§ 1 搜索
我依然記得,當年那道程序填空寫的是搜索,於是我二話不說暴搜就寫出來了,代碼如下。(還加了個剪枝)
#pragma GCC diagnostic error "-std=c++11" #include <cstdio> #include <algorithm> const int MAXN = 1005; enum class Stat { Left, Right }; enum class Dirc { Go, Back }; Stat pos[MAXN]; int N, T[MAXN], ans = 0x7f7f7f7f; void dfs(Dirc d, int rem, int tot); int main() { scanf("%d", &N); for (int i = 0; i < N; i++) { scanf("%d", &T[i]); pos[i] = Stat::Right; } dfs(Dirc::Go, N, 0); printf("%d\n", ans); return 0; } void dfs(Dirc d, int rem, int tot) { if (tot >= ans) return; int i, j; if (d == Dirc::Go) { if (rem <= 2) { int res = 0; for (i = 0; i < N; i++) if (pos[i] == Stat::Right) res = std::max(res, T[i]); ans = std::min(ans, tot + res); return; } for (i = N - 1; i > 0; i--) if (pos[i] == Stat::Right) { for (j = i - 1; j >= 0; j--) if (pos[j] == Stat::Right) { pos[i] = pos[j] = Stat::Left; dfs(Dirc::Back, rem - 2, tot + std::max(T[i], T[j])); pos[i] = pos[j] = Stat::Right; } } } else { for (i = 0; i < N; i++) if (pos[i] == Stat::Left) { pos[i] = Stat::Right; dfs(Dirc::Go, rem + 1, tot + T[i]); pos[i] = Stat::Left; } } }
這個搜索就很直白了,直接枚舉往左和往右走的人。當然,考慮到此題的數據范圍到了1000,因此對於搜索或許並不是那么友好。不出所料,上面的程序只能拿50分,剩下的點全部超時。
§ 2 問題性質?
於是我們不得不考慮其他算法。
當然,在這之前,我們不妨還是分析一下這個問題。怎么樣使得總共的時間最少?首先,兩個人過去了,若對岸仍然有人的話,那么這兩個中必有一個要把燈送回去。因此,這個“浪費”的時間,必須使它盡可能少。
那么我們是不是一直用過橋時間最少的人送燈回去就一定好呢?答案是否定的,樣例就是最好的反駁了。而且我們可以看到在“樣例解釋”中,時間最多的人居然是和時間第二多的人一起過河的。
那么我們還需要考慮一種情況,就是時間最多的人和時間第二多的人一起過河,當然了,我們絕對不會讓這兩者其中一個送燈回對岸,這是肯定不優秀的。因此,我們需要一個時間花費少的人先在對岸等候,再在這兩個“慢羊羊”過河之后,把燈送回去;當然,這個人“送燈人”在對岸等候之前,肯定還需要一個過河時間少的人帶着燈送他過河在把燈帶回。對於這兩個人,時間花費最少的兩個人必然是最佳選擇了。
參考程序
因此我們只需要一個貪心:
#include <cstdio> #include <algorithm> const int MAXN = 1005; int N, A[MAXN]; int main() { scanf("%d", &N); for (int i = 0; i < N; i++) scanf("%d", &A[i]); std::sort(A, A + N); int l = 0, r = N - 1, ans; // l,r表示當前還需處理的人區間的左端點和右端點(已排序) while (r - l >= 0) { if (r - l < 2) { ans += A[r]; break; } if (r - l < 3) { ans += A[l] + A[l + 1] + A[r]; break; } // 特判人少的兩種情況 if (A[l + 1] * 2 > A[l] + A[r - 1]) ans += A[l] * 2 + A[r] + A[r - 1]; // 考慮上面說的兩種情況,這個是時間最少的人帶時間最多的兩個過河 else ans += A[l + 1] * 2 + A[l] + A[r]; // 這種就是第二中時間最多的兩個一起過河的 r -= 2; // 其實l端點在整個過程中都不會動,因此可以只用r端點 } printf("%d\n", ans); return 0; }
總結
我們考慮一些比較復雜,暴力做復雜度又比較高的題,它不一定需要一些高級算法或數據結構,有時候,解決它的,可能是一個很簡單的方法,只是看你想不想到了。