第八周的题

本周学习最短路径问题

1、

题目:洛谷B3647 【模板】Floyd

给出一张由 n 个点 m 条边组成的无向图。求出所有点对 (i,j) 之间的最短路径。输入第一行为两个整数n,m,分别代表点的个数和边的条数。接下来 m 行,每行三个整数 u,v,w,代表 u,v 之间存在一条边权为 w 的边。输出 n 行每行 n 个整数。第 i 行的第 j 个整数代表从 i 到 j 的最短路径。

思路:

用floyd算法,解决图中所有顶点对之间最短路径

#include 
#include 
#include 

using namespace std;

const int INF = INT_MAX;

void floydWarshall(vector >& graph, int n) {
    // 使用邻接矩阵表示图,初始化距离矩阵
    vector > dist(n, vector(n, INF));

    // 将已知的边权值填入距离矩阵
    for (int i = 0; i < n; ++i) {
        dist[i][i] = 0;
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == j) continue;
            dist[i][j] = min(dist[i][j], graph[i][j]);
        }
    }

    // 使用 Floyd-Warshall 算法计算最短路径
    for (int k = 0; k < n; ++k) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }

    // 输出结果
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cout << (dist[i][j] == INF ? -1 : dist[i][j]) << " ";
        }
        cout << endl;
    }
}

int main() {
    int n, m;
    cin >> n >> m;

    // 初始化邻接矩阵
    vector > graph(n, vector(n, INF));

    // 读入边的权值
    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u - 1][v - 1] = w;
        graph[v - 1][u - 1] = w;  // 无向图,需要考虑双向
    }

    // 调用 Floyd-Warshall 算法计算最短路径
    floydWarshall(graph, n);

    return 0;
}

2、

题目:洛谷P4779 【模板】单源最短路径(标准版)

给定一个 n 个点,m 条有向边的带非负权图,请你计算从s 出发,到每个点的距离。数据保证你能从 s 出发到任意点。输入第一行为三个正整数n,m,s。 第二行起 m 行,每行三个非负整数 ui​,vi​,wi​,表示从 ui​ 到 vi​ 有一条权值为 wi​ 的有向边。输出一行 n 个空格分隔的非负整数,表示 s 到每个点的距离。

思路:

使用邻接表来表示图。通过数组 fr 记录每个顶点的邻接表的起始位置,数组 to 存储边的目标顶点,nex 存储下一条边在邻接表中的位置,v 存储边的权重。用add函数添加一条边到图中,更新邻接表。进行Dijkstra算法初始化,使用优先队列不断选择当前最短路径估计的顶点,放松其邻接点,更新最短路径估计。重复此过程直到所有顶点都被访问。

#include
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[100010],to[200010],nex[200010],v[200010],tl,d[100010];
bool b[100010];
void add(int x,int y,int w){
    to[++tl]=y;
    v[tl]=w;
    nex[tl]=fr[x];
    fr[x]=tl;
}
priority_queue< pair > q;
int main(){
    int n,m,x,y,z,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    for(int i=1;i<=n;i++) d[i]=1e10;
    d[s]=0;
    q.push(M(0,s));
    while(!q.empty()){
        int x=q.top().second;
        q.pop(); 
        if(b[x]) continue;
        b[x]=1;
        for(int i=fr[x];i;i=nex[i]){
            int y=to[i],l=v[i];
            if(d[y]>d[x]+l){
                d[y]=d[x]+l;
                q.push(M(-d[y],y));
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",d[i]);
    return 0;
}

3、

题目:洛谷P2661 [NOIP2015 提高组] 信息传递

有 n 个同学(编号为 1 到 n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti​ 的同学。游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?输入共 2 行。第一行包含 1 个正整数 n,表示 n 个人。第二行包含 n 个用空格隔开的正整数 T1​,T2​,⋯,Tn​,其中第 i 个整数 Ti​ 表示编号为 i 的同学的信息传递对象是编号为 Ti​ 的同学,Ti​≤n 且Ti ≠​i。输出共一行一个整数,表示游戏一共可以进行多少轮。

思路:

用并查集找到信息传递的环,记录最小的环的长度遍历每个同学,通过 get 函数找到信息传递的环,如果环的起点是当前同学(get(f, cnt) == i),则更新答案为最小的环长度。

#include 
using namespace std;
const int N = 200010;
int n, fa[N], ans = 0x3f3f3f3f;
int get (int x, int &cnt) { //cnt记录环的长度 
    cnt ++;
    if (fa[x] == x) return x;
    else return get(fa[x], cnt);
}
int main () {
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
        fa[i] = i;
    for (int i = 1; i <= n; i ++) {
        int cnt = 0, f;
        scanf("%d", &f);
        if (get(f, cnt) == i) {
            ans = min(ans, cnt); //维护最小的环 
        }else
        	fa[i] = f;
    }
    printf("%d", ans);
    return 0;
}

4、

题目:洛谷P1144 最短路计数

给出一个 N 个顶点 M 条边的无向无权图,顶点编号为1∼N。问从顶点 1 开始,到其他每个点的最短路有几条。输入第一行包含 2 个正整数 N,M,为图的顶点数与边数。接下来 M 行,每行 2 个正整数 x,y,表示有一条由顶点 x 连向顶点 y 的边,请注意可能有自环与重边。输出共 N 行,每行一个非负整数,第 i 行输出从顶点 1 到顶点 i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 ans mod 100003 后的结果即可。如果无法到达顶点 i 则输出 0。

思路:

使用广度优先搜索(BFS)和动态规划,通过邻接表 graph 表示图的连接关系。使用两个数组 distance 和 count 分别记录从顶点1到每个顶点的最短路径长度和路径数目,利用BFS遍历图,从顶点1开始,逐层遍历,记录每个顶点的最短路径长度和路径数目。如果发现一个顶点的最短路径长度等于当前节点的最短路径长度加1,说明存在另一条最短路径,那么将路径数目加到该节点上。如果发现一个顶点的最短路径长度为-1,说明从1无法到达该节点,直接输出0。最后输出每个顶点的最短路径数目,如果无法到达,输出0。

#include 
#include 
#include 

using namespace std;

const int MOD = 100003;

void addMod(int& a, int b) {
    a = (a + b) % MOD;
}

int main() {
    int N, M;
    cin >> N >> M;

    // 邻接表表示图
    vector > graph(N + 1);
    for (int i = 0; i < M; ++i) {
        int x, y;
        cin >> x >> y;
        graph[x].push_back(y);
        graph[y].push_back(x);  // 无向图,需要加上反向边
    }

    // BFS
    vector distance(N + 1, -1);
    vector count(N + 1, 0);
    queue q;

    distance[1] = 0;
    count[1] = 1;
    q.push(1);

    while (!q.empty()) {
        int current = q.front();
        q.pop();

        for (int neighbor : graph[current]) {
            if (distance[neighbor] == -1) {
                distance[neighbor] = distance[current] + 1;
                count[neighbor] = count[current];
                q.push(neighbor);
            } else if (distance[neighbor] == distance[current] + 1) {
                addMod(count[neighbor], count[current]);
            }
        }
    }

    // 输出结果
    for (int i = 1; i <= N; ++i) {
        if (distance[i] == -1) {
            cout << 0 << endl;
        } else {
            cout << count[i] << endl;
        }
    }

    return 0;
}

5、

题目:洛谷P8794 [蓝桥杯 2022 国 A] 环境治理

LQ 国拥有 n 个城市,从 0 到 n−1 编号,这 n 个城市两两之间都有且仅有一条双向道路连接,这意味着任意两个城市之间都是可达的。每条道路都有一个属性 D,表示这条道路的灰尘度。当从一个城市 A 前往另一个城市 B 时,可能存在多条路线,每条路线的灰尘度定义为这条路线所经过的所有道路的灰尘度之和,LQ 国的人都很讨厌灰尘,所以他们总会优先选择灰尘度最小的路线。

LQ 国很看重居民的出行环境,他们用一个指标 P 来衡量 LQ 国的出行环境,P 定义为:

P=i=0∑n−1​j=0∑n−1​d(i,j)

其中 d(i,j) 表示城市 i 到城市 j 之间灰尘度最小的路线对应的灰尘度的值。

为了改善出行环境,每个城市都要有所作为,当某个城市进行道路改善时,会将与这个城市直接相连的所有道路的灰尘度都减少 1,但每条道路都有一个灰尘度的下限值 L,当灰尘度达到道路的下限值时,无论再怎么改善,道路的灰尘度也不会再减小了。

具体的计划是这样的:

  • 第 1 天,00 号城市对与其直接相连的道路环境进行改善;
  • 第 2 天,11 号城市对与其直接相连的道路环境进行改善;

……

  • 第 n 天,n−1 号城市对与其直接相连的道路环境进行改善;
  • 第 n+1 天,0 号城市对与其直接相连的道路环境进行改善;
  • 第 n+2 天,1 号城市对与其直接相连的道路环境进行改善;

……

LQ 国想要使得 P 指标满足P≤Q。请问最少要经过多少天之后,P 指标可以满足 P≤Q。如果在初始时就已经满足条件,则输出 0;如果永远不可能满足,则输出−1。输入的第一行包含两个整数 n,Q,用一个空格分隔,分别表示城市个数和期望达到的 P 指标。接下来 n 行,每行包含 n 个整数,相邻两个整数之间用一个空格分隔,其中第 i 行第 j 列的值 Di,j​(Di,j​=Dj,i​,Di,i​=0) 表示城市 i 与城市 j 之间直接相连的那条道路的灰尘度。接下来 n 行,每行包含 n 个整数,相邻两个整数之间用一个空格分隔,其中第 i 行第 j 列的值 Li,j​(Li,j​=Lj,i​,Li,i​=0) 表示城市 i 与城市 j 之间直接相连的那条道路的灰尘度的下限值。输出一行包含一个整数表示答案。

思路:

使用二分搜索来找到达到目标污染水平所需的最小天数,通过使用 calculatePollution 函数模拟污染水平的变化,函数根据天数调整污染值,并使用 Floyd-Warshall 算法在城市网络中找到最短路径。

#include 

#define MAX_N 105
#define INF 1000000000

int pollution[MAX_N][MAX_N];
int pollutionLimit[MAX_N][MAX_N];
int n;

int min(int a, int b) {
    return a < b ? a : b;
}
//计算经过一定天数后的污染水平
int calculatePollution(int days) {
    int temp[MAX_N][MAX_N];

    // 使用初始污染值初始化辅助矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            temp[i][j] = pollution[i][j];

    // 使根据天数调整污染值
    for (int i = 0; i < n; i++) {
        int val = days / n + (days % n >= i + 1 ? 1 : 0);
        for (int j = 0; j < n; j++) {
            temp[i][j] -= val;
            if (temp[i][j] < pollutionLimit[i][j]) temp[i][j] = pollutionLimit[i][j];
            temp[j][i] -= val;
            if (temp[j][i] < pollutionLimit[j][i]) temp[j][i] = pollutionLimit[j][i];
        }
    }

    // 使用 Floyd-Warshall 算法找到最短路径
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                temp[i][j] = min(temp[i][j], temp[i][k] + temp[k][j]);

    // 计算治理后的总污染值
    int result = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            result += temp[i][j];

    return result;
}

int main() {
    int targetPollution;
    scanf("%d %d", &n, &targetPollution);

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &pollution[i][j]);

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &pollutionLimit[i][j]);

    int left = 0, right = INF, answer = -1;

    // 二分搜索最小天数
    while (left <= right) {
        int mid = (left + right) / 2;
        if (calculatePollution(mid) <= targetPollution) {
            right = mid - 1;
            answer = mid;
        } else {
            left = mid + 1;
        }
    }

    printf("%d\n", answer);

    return 0;
}

你可能感兴趣的:(算法)