2021 RoboCom机器人开发者大赛 CAIP 本科组初赛 第三题

注意, 本题解默认观看者理解dijkstra、floyd算法, 熟悉图的存储方式

题目

7-3 打怪升级(25分)

很多游戏都有打怪升级的环节,玩家需要打败一系列怪兽去赢取成就和徽章。这里我们考虑一种简单的打怪升级游戏,游戏规则是,给定有 N 个堡垒的地图,堡垒之间有道路相连,每条道路上有一只怪兽把守。怪兽本身有能量,手里的武器有价值。打败怪兽需要的能量等于怪兽本身的能量,而怪兽一旦被打败,武器就归玩家所有 —— 当然缴获的武器价值越高,玩家就越开心。

你的任务有两件:
帮助玩家确定一个最合算的空降位置,即空降到地图中的某个堡垒,使得玩家从这个空降点出发,到攻下最难攻克(即耗费能量最多)的那个堡垒所需要的能量最小;
从这个空降点出发,帮助玩家找到攻克任意一个其想要攻克的堡垒的最省能量的路径。如果这种路径不唯一,则选择沿途缴获武器总价值最高的解,题目保证这种解是唯一的。

输入格式
输入第一行给出两个正整数 N (≤1000) 和 M,其中 N 是堡垒总数,M 是怪兽总数。为简单起见,我们将堡垒从 1 到 N 编号。随后 M 行,第 i 行给出了第 i 只怪兽的信息,格式如下:

B1 B2 怪兽能量 武器价值

其中 B1 和 B2 是怪兽把守的道路两端的堡垒编号。题目保证每对堡垒之间只有一只怪兽把守,并且 怪兽能量 和 武器价值 都是不超过 100 的正整数。
再后面是一个正整数 K(≤N)和玩家想要攻克的 K 个目标堡垒的编号。

输出格式
首先在一行中输出玩家空降的堡垒编号 B0。如果有多种可能,则输出编号最小的那个。

随后依次为玩家想要攻克的每个堡垒 B 推荐最省能量的攻克路径,并列出需要耗费的能量值和沿途缴获武器的总价值。注意如果最省力的路径不唯一,则选择沿途缴获武器总价值最高的解。格式为:

B0->途经堡垒1->…->B
总耗费能量 武器总价值

输入样例

6 12
1 2 10 5
2 3 16 20
3 1 4 2
2 4 20 22
4 5 2 2
5 3 12 6
4 6 8 5
6 5 10 5
6 1 20 25
1 5 8 5
2 5 2 1
2 6 8 5
4
2 3 6 5

输出样例

5
5->2
2 1
5->1->3
12 7
5->4->6
10 7
5
0 0

题意解读

给定有 N 个堡垒的地图,堡垒之间有道路相连

解释:给定n个顶点的无向图, 且保证为连通图

每条道路上有一只怪兽把守。怪兽本身有能量,手里的武器有价值。打败怪兽需要的能量等于怪兽本身的能量

解释: 图中无重边, 每条边有两个权值, 分别为能量(距离) 和 价值(次权)

要求一

帮助玩家确定一个最合算的空降位置,即空降到地图中的某个堡垒,使得玩家从这个空降点出发,到攻下最难攻克(即耗费能量最多)的那个堡垒所需要的能量最小;

解释: 要分别求出图中每个顶点为起点的最短路径信息, 取最短路径信息中最大值最小的顶点为空降位置.要求一中只涉及路径(能量), 并没有涉及另一个权

要求二

从这个空降点出发,帮助玩家找到攻克任意一个其想要攻克的堡垒的最省能量的路径。如果这种路径不唯一,则选择沿途缴获武器总价值最高的解,题目保证这种解是唯一的。

解释: 以空降点为其它获取它的双权最短路径, 其中能量为主权, 只有能量相等时才会比较武器价值.
并且要记录路径, 后续输出.

问题分析及核心代码解释

对于要求一, 是一个多源单权最短路径问题, 使用floyd算法尤为方便.
用一个邻接矩阵存储图的能量信息, 对其使用floyd, 再在结果中找到空降点.

int g[N][N];

void floyd()
{
    for(int i=1; i<=n; i++)
        for(int x=1; x<=n; x++)
            for(int y=x+1; y<=n; y++)
                g[x][y] = g[y][x] = min(g[x][y], g[x][i]+g[i][y]);
}

//找出空降点
int Find()
{
    int res, minE = INF;
    for(int i=1; i<=n; i++)
    {
        int maxE = 0;
        for(int j=1; j<=n; j++)
            if(g[i][j] > maxE && i!=j)
                maxE = g[i][j];
        if(maxE < minE)
        {
            res = i;
            minE = maxE;
        }
    }
    return res;
}

对于要求二, 是一个单源双权最短路径问题, 使用dijkstra算法较为方便. 因为是dijkstra算法并且有双权, 所以这里用前向星来保存图的信息

//we为能量权, wv为价值权
int h[N], we[N], wv[N], e[N], ne[N], idx;

在dijkstra算法过程中我们不仅要维护最短距离/能量(dist)、状态数组vis、路径数组path, 还要维护最大价值value, 以确保最短距离的正确性

int dist[N];
int value[N];
bool vis[N];
int path[N];

为了方便, 使用了朴素版dijkstra

void dijkstra(int bg)
{
    memset(dist, 0x3f, sizeof dist);
    dist[bg] = 0;
    for(int i=1; i<=n; i++)
    {
        int t = -1;
        for(int j=1; j<=n; j++)
            if(!vis[j] && (t==-1 || dist[j] < dist[t]))
                t = j;
        for(int j=h[t]; j!=-1; j=ne[j])
        {
            int dest = e[j];
            //有两种情况要进行更新:路径较之前的小 或路径相同但价值较之前的大
            if((dist[dest] > dist[t] + we[j]) ||
            (dist[dest] == dist[t] + we[j] && value[dest] < value[t] + wv[j]))
            {
                dist[dest] = dist[t] + we[j];
                value[dest] = value[t] + wv[j];
                path[dest] = t;
            }
        }
        vis[t] = true;
    }
}

最后递归打印路径

void PrintPath(int bg, int tar)
{
    if(bg == tar)
    {
        cout << bg;
        return;
    }
    else PrintPath(bg, path[tar]);
    cout << "->" << tar;
}

完整代码

//
// Created by trudbot on 2022/7/8.
//

#include 

#define ll long long
#define pii pair
#define INF 0x3f3f3f3f
using namespace std;
#define N 1010
int n, m;
int g[N][N];
int h[N], we[N], wv[N], e[N], ne[N], idx;
int dist[N];
int value[N];
bool vis[N];
int path[N];

void add(int x, int y, int z, int v)
{
    wv[idx] = v, we[idx] = z, e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}

void floyd()
{
    for(int i=1; i<=n; i++)
        for(int x=1; x<=n; x++)
            for(int y=x+1; y<=n; y++)
                g[x][y] = g[y][x] = min(g[x][y], g[x][i]+g[i][y]);
}

int Find()
{
    int res, minE = INF;
    for(int i=1; i<=n; i++)
    {
        int maxE = 0;
        for(int j=1; j<=n; j++)
            if(g[i][j] > maxE && i!=j)
                maxE = g[i][j];
        if(maxE < minE)
        {
            res = i;
            minE = maxE;
        }
    }
    return res;
}

void dijkstra(int bg)
{
    memset(dist, 0x3f, sizeof dist);
    dist[bg] = 0;
    for(int i=1; i<=n; i++)
    {
        int t = -1;
        for(int j=1; j<=n; j++)
            if(!vis[j] && (t==-1 || dist[j] < dist[t]))
                t = j;
        for(int j=h[t]; j!=-1; j=ne[j])
        {
            int dest = e[j];
            if((dist[dest] > dist[t] + we[j]) ||
            (dist[dest] == dist[t] + we[j] && value[dest] < value[t] + wv[j]))
            {
                dist[dest] = dist[t] + we[j];
                value[dest] = value[t] + wv[j];
                path[dest] = t;
            }
        }
        vis[t] = true;
    }
}

void PrintPath(int bg, int tar)
{
    if(bg == tar)
    {
        cout << bg;
        return;
    }
    else PrintPath(bg, path[tar]);
    cout << "->" << tar;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(h, -1, sizeof h);
    memset(g, 0x3f, sizeof g);

    cin >> n >> m;
    int x, y, z, v;
    while (m--)
    {
        cin >> x >> y >> z >> v;
        g[x][y] = g[y][x] = z;
        add(x, y, z, v);
        add(y, x, z, v);
    }

    floyd();
    int bg = Find();
    cout << bg << endl;
    dijkstra(bg);
    int k; cin >> k;
    int tar;
    while(k--)
    {
        cin >> tar;
        PrintPath(bg, tar);
        cout << endl;
        cout << dist[tar] << " " << value[tar] << endl;
    }
    return 0;
}

你可能感兴趣的:(算法优质题解计划,算法,c++,开发语言,图搜索算法)