过桥问题

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

总结

我们考虑一些比较复杂,暴力做复杂度又比较高的题,它不一定需要一些高级算法或数据结构,有时候,解决它的,可能是一个很简单的方法,只是看你想不想到了。

posted @ 2018-07-04 16:08  CaptainSlow  阅读(2279)  评论(0编辑  收藏  举报