【题解】导游(宁波小学2008第3题)(关于DFS和剪枝)

 

题目描述

宁波市的中小学生们在镇海中学参加程序设计比赛之余,热情的主办方邀请同学们参观镇海中学内的各处景点,已知镇海中学内共有n处景点。现在有n位该校的学生志愿承担导游和讲解任务。每个学生志愿者对各个景点的熟悉程度是不同的,如何将n位导游分配至n处景点,使得总的熟悉程度最大呢?要求每个景点处都有一个学生导游。

输入

输入文件daoyou.in中有若干行:

第一行只有一个正整数n,表示有n个景点和n个学生导游。

第二行至第n+1行共n行,每行有n个以空格分隔的正整数。第i+1行的第j个数k(1≤k≤1000),表示第i个学生导游对景点j的熟悉程度为k。

输出

输出文件daoyou.out只有一行,该行只有一个正整数,表示求得的熟悉程度之和的最大值。

样例输入

3 10 6 8 9 2 3 1 7 2

样例输出

24

数据范围

50%的数据,1≤n≤9。

100%的数据,1≤n≤17。


以上为题意描述及数据范围(废话)

分析

首先,应该能想到最最暴力的解法 —— DFS,分成n层,每层进行n次遍历,没找到未被标记的点就接着往下搜,并同时标记,然后再回溯,同时取消标记。

这个方法很容易,但是看看数据结构就可以知道,最多50分;

50分代码:

#include <bits/stdc++.h>
using namespace std;
 
struct stu
{
    int a[20];
}s[20];
 
int n,maximum = 0;
bool vis[20] = {false};
 
void dfs(int depth, int sum){
    if(depth >= n){
        if(sum > maximum)
            maximum = sum;
        return;
    }
 
    for (int i = 0; i < n; ++i)
    {
        if(!vis[i]){
            vis[i] = true;
            dfs(depth+1, sum + s[i].a[depth]);
            vis[i] = false;
        }
    }
}
 
int main()
{
    scanf("%d",&n);
    for (int i = 0; i < n; ++i){
        for (int j = 0; j < n; ++j){
            scanf("%d",&s[i].a[j]);
        }
    }
 
    dfs(0,0);
    printf("%d\n", maximum);
 
    return 0;
}

然后,既然用了DFS,那就一定会和剪枝有关,所谓剪枝,就是提前判断是否需要搜索,如果不需要就提前把这一路的枝条剪掉,以节省时间。

那么在这个题中,我们可以把每个数减掉1000,那么就会变成一个负数,而我们求的依然是最大值,那么此时只要在搜索过程中发现某一时刻,累加的值小于当前最大值,则可以直接剪掉,因为每个数都已经是负数了,当你已经小于最大值的时候,你不管加多少都不可能大于最大值。

#include <bits/stdc++.h>
using namespace std;
 
struct stu
{
    int a[20];
}s[20];
 
int n,maximum = -20000;
bool vis[20] = {false};
int sum[20] = {0};
 
void dfs(int depth, int t){
    if(depth > n){
        if(t > maximum)
            maximum = t;
        return;
    }
    if(t < maximum)
        return;
 
    for (int i = 1; i <= n; ++i)
    {
        if(!vis[i]){
            vis[i] = true;
            dfs(depth+1, t + s[i].a[depth]);
            vis[i] = false;
        }
    }
}
 
int main()
{
    scanf("%d",&n);
    for (int i = 1; i <= n; ++i){
        for (int j = 1; j <= n; ++j){
            scanf("%d",&s[i].a[j]);
            s[i].a[j] -= 1000;
        }
    }
 
    dfs(1,0);
    printf("%d\n", maximum + n*1000);
 
    return 0;
}

或者,我们可以倒着做一个前缀和,记录当前层数后面最大的熟悉程度和,类似于启发式搜索,但只是为了剪枝。当当前熟悉度加上后面最大的熟悉度也无法达到当前最大值的话,就不需要继续搜索了

代码

#include <bits/stdc++.h>
using namespace std;
 
int n;
int s[20];
int a[20][20];
int vis[20];
int ans;
 
void dfs(int step, int sum){
    if(step == n){
        ans = max(ans,sum);
        return;
    }
    if(sum + s[step] <= ans)
        return;
    for (int i = 0; i < n; ++i) {
        if(!vis[i]) {
            vis[i] = 1;
            dfs(step + 1, sum + a[step][i]);
            vis[i] = 0;
        }
    }
}
 
int main()
{
    cin >> n;
    int m = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cin >> a[i][j];
            m = max(m,a[i][j]);
        }
        s[i] = m;
    }
    for (int i = n-1; i >= 0; --i) {
        s[i] += s[i+1];
    }
    dfs(0,0);
    cout << ans << endl;
 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30115697/article/details/81940977