過橋問題


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;
}

總結

我們考慮一些比較復雜,暴力做復雜度又比較高的題,它不一定需要一些高級算法或數據結構,有時候,解決它的,可能是一個很簡單的方法,只是看你想不想到了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM