You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

VUA~125 Numbering Paths

题目传送门

一、题目描述

给定一些组数据,每组数据由一个 nn 对整数 jk 组成。由EOF停止读入。每组 j_ik_i 意味着存在一条由点 j_i到点 k_i​的 单向边。其中,所有 j_ik_i中的最大值 +1 即为 最终矩阵边长 a。可能存在 自环。数据保证任意一个点的入度和出度都不大于 30

每组输出,包括一个字符串matrix for city,一个整数 t 表示从 0 开始的组数,以及一个 a\times a 的矩阵 M,其中第 i 行第 j 个元素 M[i][j] 表示从点 ij不同路径数量。如果有无数条,在该位置输出 -1严格要求行末没有空格

二、题目解析

前置芝士:Floyd

Floyd 是一种求多源最短路的方法,其本质思想是动态规划。

dp[i][j] 表示 i 节点到 j 节点之间的 最短路,那么可以得到状态转移方程:

\large dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])(1≤k≤n)

注意 dp 时外层循环先枚举 k,因为 Floyd 本来是一个三维 dp dp[k][i][j] 表示 i 节点到 j 节点之间只能经过 1 \sim k 这几个节点的最短路),通过类似完全背包的空间优化才把空间复杂度降到二维。

具体做法 对于这道题,我们可以对状态转移方程进行修改。 设 dp[i][j] 表示 i 节点到 j 节点之间的路径个数。同样的道理,我们需要枚举中间跳板 k

显然,根据乘法原理 i 节点到 j 节点之间的路径个数 等于 i 节点到 k 节点之间的路径个数 与 k 节点到 j 节点之间的路径个数 相乘

可以得到状态状态转移方程:

\large dp[i][j] = dp[i][j] + dp[i][k] \times dp[k][j](1 \le k \le n)

怎么判断路径个数无数呢?

显然,路径个数无数当且只当图中有环时出现。当一个节点到达自己的路径数不是 0 时,那么它就是图上环的一部分。

三、实现代码

#include <bits/stdc++.h>
using namespace std;

const int N = 30;
int m, n, t;
int dp[N][N];

void floyd() {
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                dp[i][j] += dp[i][k] * dp[k][j]; //记录边数
}

int main() {
    while (scanf("%d", &m) != EOF) {
        int a, b;
        n = 0;
        memset(dp, 0, sizeof(dp));

        while (m--) {
            scanf("%d%d", &a, &b);
            dp[a][b] = 1;       //单向边同时边权为1
            n = max({n, a, b}); //最大节点号
        }

        //注意有0节点所以矩阵边长为n+1。
        n++;

        floyd();

        printf("matrix for city %d\n", t++);

        //结论某个节点到自己的距离不为0就是有环
        for (int k = 0; k < n; k++)
            if (dp[k][k]) { //如果存在环的话,需要特殊处理
                for (int i = 0; i < n; i++)
                    for (int j = 0; j < n; j++)
                        if (dp[i][k] && dp[k][j]) //如果有其它点i,j利用过k则i->k,k->j都是无数条路径
                            dp[i][j] = -1;
                dp[k][k] = -1; //标识k->k也有无数条路径
            }

        //严格控制格式,输出矩阵
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++)
                if (j == 0)
                    printf("%d", dp[i][j]);
                else
                    printf(" %d", dp[i][j]);
            puts("");
        }
    }
    return 0;
}