Files
python/TangDou/CSP-J/AcWingTraining/T8/452.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

6.1 KiB
Raw Blame History

AcWing 452. 文化之旅

一、题目描述

有一位使者要游历各国,他每到一个国家,都能学到一种文化,但他不愿意学习任何一种文化超过一次(即如果他学习了某种文化,则他就不能到达其他有这种文化的国家)。

不同的国家可能有相同的文化。

不同文化的国家对其他文化的看法不同,有些文化会排斥外来文化(即如果他学习了某种文化,则他不能到达排斥这种文化的其他国家)。 

注:仔细阅读,整明白是谁排斥谁,别整反了。

现给定各个国家间的地理关系,各个国家的文化,每种文化对其他文化的看法,以及这位使者游历的起点和终点(在起点和终点也会学习当地的文化),国家间的道路距离,试求从起点到终点最少需走多少路。

输入格式 第一行为五个整数 $N,K,M,S,T$,每两个整数之间用一个空格隔开,依次代表国家个数(国家编号为 1 到 $N$),文化种数(文化编号为 1 到 $K$),道路的条数,以及起点和终点的编号(保证 S 不等于 $T$)。

第二行为 N 个整数,每两个整数之间用一个空格隔开,其中第 i 个数 $C_i$,表示国家 i 的文化为 $C_i$。 

接下来的 K 行,每行 K 个整数,每两个整数之间用一个空格隔开,记第 i 行的第 j 个数为 $a_{i,j}$,a_{i,j}= 1 表示文化 i 排斥外来文化 $j$(i 等于 j 时表示排斥相同文化的外来人),a_{i,j}= 0 表示不排斥(注意 i 排斥 j 并不保证 j 一定也排斥 $i$)。

接下来的 M 行,每行三个整数 $u,v,d$,每两个整数之间用一个空格隔开,表示国家 u 与国家 v 有一条距离为 d 的可双向通行的道路(保证 u 不等于 $v$,两个国家之间可能有多条道路)。

输出格式 输出只有一行,一个整数,表示使者从起点国家到达终点国家最少需要走的距离数(如果无解则输出 $−1$)。

数据范围 2≤N≤100,1≤K≤100,1≤M≤N2,1≤Ci≤K,1≤u,v≤N,1≤d≤1000,1≤S,T≤N,S≠T

样例 输入样例:

2 2 1 1 2
1 2
0 1
0 0
1 2 10

输出样例:

10

二、算法

暴力+剪枝+ $floyd$

有文化限制 不能直接最短路去做

数据范围较小 可以用$floyd$水过

启发式剪枝 不考虑限制的最短路距离一定是小于等于考虑限制的距离 $d[i][j]$表示$i->j$的最短距离 $ans$表示程序求出的解 $dist$表示从起点到$u$的距离 那么

\large dist + d[u][T] <= ans

那么dist + d[u][T] >= ans 的$ans$就是无用的,可以continue

再加一点玄学优化,倒着搜,正着搜会被卡,其余的直接暴力

三、实现代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
int n, K, m, S, T;
int c[N];         // 文化
int cg[N][N];     // 互斥关系
int g[N][N];      // 地图,不一定连通
int d[N][N];      // floyd用的多源最短路数组
int path[N], top; // 记录已经学过的文化
int ans = INF;    // 答案

void dfs(int u, int dist) {            // 从u出发,dist:已经走的步数
    if (dist + d[S][u] >= ans) return; // 启发式剪枝,如果已经走的步数+从u到S的最短距离,已经大于目前的最小答案,那么,此路不通
    if (u == S) {                      // 如果走到了S,收集答案
        ans = dist;
        return;
    }

    // 还没有走到起点,从哪个点走过来的
    for (int i = 1; i <= n; i++) {
        if (g[i][u] < INF) {                // 可能从i点过来的
            bool flag = true;               // 文件上没有排斥
            for (int j = 0; j < top; j++) { // 遍历一下所有已经走过的文化,是不是存在文化上的冲突
                if (cg[path[j]][c[i]]) {    // 如果i国家的文化,与后面已知国家的文化冲突,那么一定不是从i过去的
                    flag = false;           // 不可能从i走到u
                    break;
                }
            }
            if (flag) {                 // 文化上没有排斥
                path[top++] = c[i];     // i号城市的文化c[i]进入数组
                dfs(i, dist + g[i][u]); // 继续反向搜索
                top--;                  // 回溯
            }
        }
    }
}

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);

    // 一般在数据量大于10W时考虑用scanf,如果scanf过不去,再考虑使用快读
    cin >> n >> K >> m >> S >> T;

    // 先读入文化
    for (int i = 1; i <= n; i++) cin >> c[i];
    for (int i = 1; i <= K; i++) // 读入排斥关系
        for (int j = 1; j <= K; j++)
            cin >> cg[i][j];
    // 每个文化排斥自己
    for (int i = 1; i <= K; i++) cg[i][i] = 1;

    // 两个国家之间可能有多条道路,可能有重边
    memset(g, 0x3f, sizeof g);                // 任意两点间距离设置为正无穷,表示不连通
    for (int i = 1; i <= n; i++) g[i][i] = 0; // 一开始每个国家都与自己连通
    // 读入所有的边
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    // Floyd 用于启发式剪枝,提前预处理出任意两点间的最短距离
    memcpy(d, g, sizeof d);
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

    path[top++] = c[T];       // 直播时忘记加入终点了,初始化终点
    dfs(T, 0);                // 从后往前搜索,玄学倒序,专克出题人
    if (ans == INF) ans = -1; // 没有任何一个能走到S的,说明无解
    cout << ans << endl;
    return 0;
}