'commit'
This commit is contained in:
32
TangDou/AcWing/Floyd/1471.cpp
Normal file
32
TangDou/AcWing/Floyd/1471.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int n, d[N][N], ans;
|
||||
int main() {
|
||||
scanf("%d", &n);
|
||||
|
||||
int u, v;
|
||||
for (int i = 1; i < n; i++) scanf("%d%d", &u, &v), d[u][v] = 1;
|
||||
|
||||
// Floyd求传递闭包
|
||||
for (int k = 1; k <= n; k++)
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 1; j <= n; j++)
|
||||
d[i][j] |= d[i][k] & d[k][j];
|
||||
|
||||
for (int i = 1; i <= n; i++) {
|
||||
int cnt = 0;
|
||||
for (int j = 1; j <= n; j++)
|
||||
if (d[j][i]) cnt++; //有多少个点可以到达i点
|
||||
|
||||
if (cnt == n - 1) {
|
||||
ans = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ans)
|
||||
printf("%d", ans);
|
||||
else
|
||||
puts("-1");
|
||||
return 0;
|
||||
}
|
||||
103
TangDou/AcWing/Floyd/1471.md
Normal file
103
TangDou/AcWing/Floyd/1471.md
Normal file
@@ -0,0 +1,103 @@
|
||||
## [$AcWing$ $1471$. 牛奶工厂](https://www.acwing.com/solution/content/90857/)
|
||||
|
||||
### 一、题目描述
|
||||
牛奶生意正红红火火!
|
||||
|
||||
农夫约翰的牛奶加工厂内有 $N$ 个加工站,编号为 $1…N$,以及 $N−1$ 条通道,每条连接某两个加工站。(通道建设很昂贵,所以约翰选择使用了最小数量的通道,使得从每个加工站出发都可以到达所有其他加工站)。
|
||||
|
||||
为了创新和提升效率,约翰在每条通道上安装了传送带。
|
||||
|
||||
不幸的是,当他意识到传送带是单向的已经太晚了,现在每条通道只能沿着一个方向通行了!
|
||||
|
||||
所以现在的情况不再是从每个加工站出发都能够到达其他加工站了。
|
||||
|
||||
然而,约翰认为事情可能还不算完全失败,只要至少还存在一个加工站 $i$ 满足从其他每个加工站出发都可以到达加工站 $i$。
|
||||
|
||||
注意从其他任意一个加工站 $j$ 前往加工站 $i$ 可能会经过 $i$ 和 $j$ 之间的一些中间站点。
|
||||
|
||||
请帮助约翰求出是否存在这样的加工站 $i$。
|
||||
|
||||
**输入格式**
|
||||
输入的第一行包含一个整数 $N$,为加工站的数量。
|
||||
|
||||
以下 $N−1$行每行包含两个空格分隔的整数 $a_i$ 和 $b_i$,满足 $1≤a_i,b_i≤N$ 以及 $a_i≠b_i$。
|
||||
|
||||
这表示有一条从加工站 $a_i$ 向加工站 $b_i$ 移动的传送带,仅允许沿从 $a_i$ 到 $b_i$ 的方向移动。
|
||||
|
||||
**输出格式**
|
||||
如果存在加工站 $i$ 满足可以从任意其他加工站出发都可以到达加工站 $i$,输出最小的满足条件的 $i$。
|
||||
|
||||
否则,输出 $−1$。
|
||||
|
||||
**数据范围**
|
||||
$1≤N≤100$
|
||||
|
||||
**输入样例**:
|
||||
```cpp {.line-numbers}
|
||||
3
|
||||
1 2
|
||||
3 2
|
||||
```
|
||||
**输出样例**:
|
||||
```cpp {.line-numbers}
|
||||
2
|
||||
```
|
||||
|
||||
### 二、题目解析
|
||||
|
||||
**传递闭包** 问题就是一类具有传递性的问题。
|
||||
|
||||
按人话来说就是: 在一个元素集里,对你说一堆:某两个元素之间有关系。然后问你这些元素中一共有多少个元素有关系。
|
||||
|
||||
**传递闭包概念的重点**:
|
||||
这个关系必须是二元的,也就是说,其他的多元关系也一定要可以分解为几个二元关系的累积。
|
||||
|
||||
**传递闭包问题的转化和解决**
|
||||
可以将传递闭包问题转化为图论问题。
|
||||
|
||||
把元素变成一个点,有关系就连一条边。
|
||||
|
||||
最后用$Floyd$算法解决两点之间的连通关系。(任意两点)
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210818193426342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzNjgwNDA1,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
基本思路:若$i$能到$k$,$k$能到$j$,则$i$一定能到达$j$。
|
||||
|
||||
利用$Floyd$传递闭包,求解所有点互相到达的情况,$1$表示可以,$0$表示不可以。
|
||||
最后$for$循环$1$~$n$求出对于第$i$个点有多少个点能够到达它,如果等于$n-1$就直接输出这个$i$,退出循环即可。
|
||||
|
||||
### 三、实现代码
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int n, d[N][N], ans;
|
||||
int main() {
|
||||
scanf("%d", &n);
|
||||
|
||||
int u, v;
|
||||
for (int i = 1; i < n; i++) scanf("%d%d", &u, &v), d[u][v] = 1;
|
||||
|
||||
// Floyd求传递闭包
|
||||
for (int k = 1; k <= n; k++)
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 1; j <= n; j++)
|
||||
d[i][j] |= d[i][k] & d[k][j];
|
||||
|
||||
for (int i = 1; i <= n; i++) {
|
||||
int cnt = 0;
|
||||
for (int j = 1; j <= n; j++)
|
||||
if (d[j][i]) cnt++; //有多少个点可以到达i点
|
||||
|
||||
if (cnt == n - 1) {
|
||||
ans = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ans)
|
||||
printf("%d", ans);
|
||||
else
|
||||
puts("-1");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
72
TangDou/AcWing/Floyd/205.cpp
Normal file
72
TangDou/AcWing/Floyd/205.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 10; //这个黄海实测,写成3也可以AC,考虑到矩阵乘法的维度,一般写成10,差不多的都可以过掉
|
||||
const int MOD = 10000;
|
||||
|
||||
int base[N][N], res[N][N];
|
||||
int t[N][N];
|
||||
|
||||
//矩阵乘法,为快速幂提供支撑
|
||||
inline void mul(int c[][N], int a[][N], int b[][N]) {
|
||||
memset(t, 0, sizeof t);
|
||||
|
||||
//一个套路写法
|
||||
for (int i = 1; i <= 2; i++)
|
||||
for (int k = 1; k <= 2; k++) {
|
||||
if (!a[i][k]) continue; //对稀疏矩阵很有用
|
||||
for (int j = 1; j <= 2; j++)
|
||||
t[i][j] = (t[i][j] + (a[i][k] * b[k][j]) % MOD) % MOD;
|
||||
}
|
||||
memcpy(c, t, sizeof t);
|
||||
}
|
||||
|
||||
//快速幂
|
||||
void qmi(int k) {
|
||||
memset(res, 0, sizeof res);
|
||||
res[1][1] = res[1][2] = 1; //结果是一个横着走,一行两列的矩阵
|
||||
// P * M 与 M * Q 的矩阵才能做矩阵乘积,背下来即可
|
||||
//矩阵快速幂,就是乘k次 base矩阵,将结果保存到res中
|
||||
//本质上就是利用二进制+log_2N的特点进行优化
|
||||
while (k) {
|
||||
//比如 1101
|
||||
if (k & 1) mul(res, res, base); // res=res*b
|
||||
mul(base, base, base); //上一轮的翻番base*=base
|
||||
k >>= 1; //不断右移
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n;
|
||||
while (cin >> n) {
|
||||
if (n == -1) break;
|
||||
if (n == 0) {
|
||||
puts("0");
|
||||
continue;
|
||||
}
|
||||
if (n <= 2) {
|
||||
puts("1");
|
||||
continue;
|
||||
}
|
||||
|
||||
// base矩阵
|
||||
memset(base, 0, sizeof base);
|
||||
/**
|
||||
1 1
|
||||
1 0
|
||||
|
||||
第一行第一列为1
|
||||
第一行第二列为1
|
||||
第二行第一列为1
|
||||
第二行第二列为0 不需要设置,默认值
|
||||
*/
|
||||
base[1][1] = base[1][2] = base[2][1] = 1;
|
||||
|
||||
//快速幂
|
||||
qmi(n - 2);
|
||||
|
||||
//结果
|
||||
printf("%d\n", res[1][1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
205
TangDou/AcWing/Floyd/205.md
Normal file
205
TangDou/AcWing/Floyd/205.md
Normal file
@@ -0,0 +1,205 @@
|
||||
## $AcWing$ $205$. 斐波那契
|
||||
|
||||
[题目传送门](https://www.luogu.com.cn/problem/P1962)
|
||||
|
||||
### 一、题目描述
|
||||
在斐波那契数列中,$F_ib_0=0,F_ib_1=1,F_ib_n=F_ib_{n−1}+F_ib_{n−2}(n>1)$。
|
||||
|
||||
给定整数 $n$,求 $F_ib_n~ mod ~ 10000$。
|
||||
|
||||
**输入格式**
|
||||
输入包含多组测试用例。
|
||||
|
||||
每个测试用例占一行,包含一个整数 $n$。
|
||||
|
||||
当输入用例 $n=−1$ 时,表示输入终止,且该用例无需处理。
|
||||
|
||||
**输出格式**
|
||||
每个测试用例输出一个整数表示结果。
|
||||
|
||||
每个结果占一行。
|
||||
|
||||
**数据范围**
|
||||
$0≤n≤2×10^9$
|
||||
|
||||
### 二、矩阵乘法
|
||||
|
||||
设 $A$ 为 $P \times M$的矩阵,$B$ 为 $M \times Q$ 的矩阵,设矩阵 $C$ 为矩阵 $A$ 与 $B$ 的乘积,
|
||||
|
||||
其中矩阵 $C$ 中的第 $i$ 行第 $j$ 列元素可以表示为:
|
||||
|
||||
$$\large C_{i,j} = \sum_{k=1}^MA_{i,k}B_{k,j}$$
|
||||
|
||||
如果没看懂上面的式子,没关系。通俗的讲,在矩阵乘法中,结果 $C$ 矩阵的第 $i$ 行第 $j$ 列的数,就是由矩阵 $A$ 第 $i$ 行 $M$ 个数与矩阵 $B$ 第 $j$ 列 $M$ 个数分别 **①相乘** 再 **②相加** 得到的。
|
||||
|
||||
> 相关性质
|
||||
乘法结合律:$(AB)C=A(BC)(AB)C=A(BC)$
|
||||
单位矩阵:$AE=EA=A$,其中的单位矩阵 $E$ 符合:行数等于列数,对角线上的元素都是 $1$,其余都是 $0$
|
||||
|
||||
### 三、本题题解
|
||||
|
||||
如果有一道题目让你求斐波那契数列第 $n$ 项的值,**最简单的方法** 莫过于直接 **递推**。但是如果 $n$ 的范围达到了 $10^{9}$级别,递推就不行了,稳稳 $TLE$, 考虑 **矩阵加速递推**:
|
||||
|
||||
|
||||
斐波那契数列($Fibonacci$ $Sequence$)大家应该都非常的熟悉了。在斐波那契数列当中,
|
||||
|
||||
我们知道,斐波那契数列有这样的定义:
|
||||
|
||||
$$\large f_i = f_{i−1}+f_{i−2}$$
|
||||
|
||||
那么如果我们有一个$2 × 2 $的矩阵,其中第一行分别是$f_{i − 1}$ 和$f_{i-2}$。我们的目标是把第一行乘上一个矩阵变成$f_i$和$f_{i-1}$。那么应该怎么办呢?
|
||||

|
||||
|
||||
首先,矩阵$A$和矩阵$C$都含有$f_{i-1}$ 这一项。那么就先从这里下手。
|
||||
我们知道,矩阵$C$的$f_{i-1}$在第$1$行第$2$列。那么,根据公式,可以得到
|
||||
$$\large C_{1 , 2} = A_{ 1 , 1} × B_{1,2} + A_{1 , 2} × B_{ 2 , 2}$$
|
||||
|
||||
也就是说
|
||||
$$\large f_{i-1}=f_{i-1}\times B_{1,2} +f_{i-2}\times B_{2,2}$$
|
||||
|
||||
|
||||
那么很明显,我们可以得到$B_{1,2}=1,B_{2,2}=0$。这样可以保证进行矩阵乘法之后$C_{1,2}$ 是$f_{i-1}$。
|
||||

|
||||
|
||||
那么现在来看矩阵$C$中的$f_i$。我们要保证的是
|
||||
$$\large C_{1,1} =A_{1,1} ×B_{1,1} +A_{1,2} ×B_{2,1}$$
|
||||
|
||||
也就是说
|
||||
$$\large f_{i}=f_{i-1}\times B_{1,1}+f_{i-2}\times B_{2,1}$$
|
||||
|
||||
|
||||
我们知道,$f_i=f_{i-1}+f_{i-2}$。所以可以得到$B_{1,1}=1,B_{2,1}=1$
|
||||

|
||||
|
||||
那么整个矩阵$B$都被我们求出来了。
|
||||
得到了$f_i$ 和$f_{i-1}$ 后,我们再将它乘一次矩阵$B$,就可以得到$f_{i+1}$和$f_i$,又可以得到$f_{i+2}$和$f_{i+1}...$,这样就可以得到$f_n$了。
|
||||
|
||||
**但是!**
|
||||
你以为就结束了?
|
||||
这样的时间复杂度是$O(nm^2)$,其中$n$表示求斐波那契数列的第$n$项,$m$表示矩阵的长宽,还不如递推。而且递推可以得到$1$到$n$的所有斐波那契数,而矩阵乘法只能求第$n项。
|
||||
|
||||
其实还有个地方可以优化。
|
||||
我们求$f_n$的时候其实是将原矩阵$A$乘了$n − 1$次矩阵$B$的。也就是说
|
||||
$$\large 目标矩阵=A×B^{n−1}$$
|
||||
|
||||
看到$n − 1$次方想到了什么?
|
||||
可以用 **快速幂**!
|
||||
我们用快速幂的思想求出$B^{n-1}$,然后再乘上一个矩阵$A$即可。
|
||||
|
||||
怎么用快速幂?
|
||||
其实是一个道理。只不过把矩阵$A$乘矩阵$B$换成矩阵$B$乘矩阵$B$就可以了。
|
||||
那么最终的时间复杂度为$O(m^3logn)$。还是很优秀的。
|
||||
|
||||
|
||||
<font color='red' size=4><b>注意</b></font>
|
||||
矩阵乘法不满足交换律,所以一定不能写成 $\left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]^{n-2} \times \left[\begin{array}{ccc}1 & 1\end{array}\right]$ 的第一行第一列元素。
|
||||
|
||||
* 对于 $n \leq 2$ 的情况,直接输出 $1$ 即可,不需要执行矩阵快速幂。
|
||||
|
||||
* 为什么要乘上 $base$ 矩阵的 $n-2$ 次方而不是 $n$ 次方呢?因为 $F_1, F_2$ 是不需要进行矩阵乘法就能求的。也就是说,如果只进行一次乘法,就已经求出 $F_3$了。如果还不是很理解为什么幂是 $n-2$,建议手算一下。
|
||||
|
||||
### 四、实现代码
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 10; //这个黄海实测,写成3也可以AC,考虑到矩阵乘法的维度,一般写成10,差不多的都可以过掉
|
||||
const int MOD = 10000;
|
||||
|
||||
int base[N][N], res[N][N];
|
||||
int t[N][N];
|
||||
|
||||
//矩阵乘法,为快速幂提供支撑
|
||||
inline void mul(int C[][N], int A[][N], int B[][N]) {
|
||||
memset(t, 0, sizeof t);
|
||||
for (int k = 1; k <= 2; k++)
|
||||
for (int i = 1; i <= 2; i++)
|
||||
for (int j = 1; j <= 2; j++)
|
||||
t[i][j] = (t[i][j] + (A[i][k] * B[k][j]) % MOD) % MOD;
|
||||
memcpy(C, t, sizeof t);
|
||||
}
|
||||
|
||||
//快速幂
|
||||
void qmi(int k) {
|
||||
memset(res, 0, sizeof res);
|
||||
res[1][1] = res[1][2] = 1; //结果是一个横着走,一行两列的矩阵
|
||||
// P * M 与 M * Q 的矩阵才能做矩阵乘积,背下来即可
|
||||
//矩阵快速幂,就是乘k次 base矩阵,将结果保存到res中
|
||||
//本质上就是利用二进制+log_2N的特点进行优化
|
||||
while (k) {
|
||||
//比如 1101
|
||||
if (k & 1) mul(res, res, base); // res=res*b
|
||||
mul(base, base, base); //上一轮的翻番base*=base
|
||||
k >>= 1; //不断右移
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n;
|
||||
while (cin >> n) {
|
||||
if (n == -1) break;
|
||||
if (n == 0) {
|
||||
puts("0");
|
||||
continue;
|
||||
}
|
||||
if (n <= 2) {
|
||||
puts("1");
|
||||
continue;
|
||||
}
|
||||
|
||||
// base矩阵
|
||||
memset(base, 0, sizeof base);
|
||||
/**
|
||||
1 1
|
||||
1 0
|
||||
|
||||
第一行第一列为1
|
||||
第一行第二列为1
|
||||
第二行第一列为1
|
||||
第二行第二列为0 不需要设置,默认值
|
||||
*/
|
||||
base[1][1] = base[1][2] = base[2][1] = 1;
|
||||
|
||||
//快速幂
|
||||
qmi(n - 2);
|
||||
|
||||
//结果
|
||||
printf("%d\n", res[1][1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
### 五、思考题
|
||||
https://blog.csdn.net/weixin_42638946/article/details/115872469
|
||||
|
||||
使用 **快速幂优化** 计算以下数列第 $n$ 项 $mod ~10^9+7$ 的值:
|
||||
|
||||
1. $\large a_n=a_{n−1}+2a_{n−2}~(a_1=0,a_2=1)$
|
||||
|
||||
**解**:$\large F_n=F_{n-1}+2F_{n-2}$
|
||||
|
||||
$\large G(n)=[F_{n},F_{n-1}],G(n-1)=[F_{n-1},F_{n-2}]$
|
||||
|
||||
思考 $G(n-1)$ 如何变化可以到达 $G(n)$ ?
|
||||
$$\large G(n)=G(n-1) \times \left[\begin{array}{ccc} 1 & 1 \\ 2 & 0 \end{array}\right] \\
|
||||
\Rightarrow \\
|
||||
\large [F_{n},F_{n-1}]=[F_{n-1},F_{n-2}] \times \left[\begin{array}{ccc} 1 & 1 \\ 2 & 0 \end{array}\right] \\
|
||||
\Rightarrow $$
|
||||
|
||||
$$\large =[F_{2},F_{1}] \times \left[\begin{array}{ccc} 1 & 1 \\ 2 & 0 \end{array}\right]^{n-1}=[1,0] \times \left[\begin{array}{ccc} 1 & 1 \\ 2 & 0 \end{array}\right]^{n-1}$$
|
||||
|
||||
|
||||
---
|
||||
|
||||
2. $\large a_n=3a_{n−1}−2a_{n−2}+1 ~(a_1=0,a_2=1)$
|
||||
|
||||
3. $\large \displaystyle a_n=\sum_{i=1}^{n−1}[(n−i)×a_i]~(a_1=1)$
|
||||
|
||||
4. $\large a_n=a_{n−1}×a_{n−2}~(a_1=2,a_2=3)$
|
||||
|
||||
矩阵快速幂的一些题目
|
||||
https://blog.csdn.net/ypeij/article/details/118785530
|
||||
|
||||
矩阵快速幂
|
||||
https://www.cnblogs.com/mk-oi/p/13591627.html
|
||||
43
TangDou/AcWing/Floyd/854.cpp
Normal file
43
TangDou/AcWing/Floyd/854.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 210;
|
||||
const int INF = 0x3f3f3f3f;
|
||||
|
||||
int n, m, k;
|
||||
int d[N][N];
|
||||
|
||||
// 算法结束后,d[a][b]表示a到b的最短距离
|
||||
void floyd() {
|
||||
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]);
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n >> m >> k;
|
||||
// floyd初始化
|
||||
memset(d, 0x3f, sizeof d); // 任意两点间距离正无穷
|
||||
for (int i = 0; i < N; i++) d[i][i] = 0; // 自己和自己是距离为0的
|
||||
// 读入数据
|
||||
while (m--) {
|
||||
int a, b, c;
|
||||
cin >> a >> b >> c;
|
||||
d[a][b] = min(d[a][b], c); // 保留最短边.(可能有重边,保留最短边)
|
||||
}
|
||||
// 调用floyd
|
||||
floyd();
|
||||
|
||||
// 处理所有询问
|
||||
while (k--) {
|
||||
int a, b;
|
||||
cin >> a >> b;
|
||||
// 由于有负权边存在所以约大过INF/2也很合理
|
||||
if (d[a][b] > INF / 2)
|
||||
puts("impossible");
|
||||
else
|
||||
printf("%d\n", d[a][b]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
110
TangDou/AcWing/Floyd/854.md
Normal file
110
TangDou/AcWing/Floyd/854.md
Normal file
@@ -0,0 +1,110 @@
|
||||
## [$AcWing$ $854$. $floyd$ 求最短路](https://www.acwing.com/problem/content/description/856/)
|
||||
|
||||
### 一、题目描述
|
||||
给定一个 $n$ 个点 $m$ 条边的有向图,图中可能存在重边和自环,边权可能为负数。
|
||||
|
||||
再给定 $k$ 个询问,每个询问包含两个整数 $x$ 和 $y$,表示查询从点 $x$ 到点 $y$的最短距离,如果路径不存在,则输出 `impossible`。
|
||||
|
||||
数据保证图中不存在负权回路。
|
||||
|
||||
**输入格式**
|
||||
第一行包含三个整数 $n,m,k$。
|
||||
|
||||
接下来 $m$ 行,每行包含三个整数 $x,y,z$,表示存在一条从点 $x$ 到点 $y$ 的有向边,边长为 $z$。
|
||||
|
||||
接下来 $k$ 行,每行包含两个整数 $x,y$,表示询问点 $x$ 到点 $y$ 的最短距离。
|
||||
|
||||
**输出格式**
|
||||
共 $k$ 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 `impossible`。
|
||||
|
||||
**数据范围**
|
||||
$1≤n≤200,1≤k≤n^2,1≤m≤20000$,
|
||||
图中涉及边长绝对值均不超过 $10000$。
|
||||
|
||||
**输入样例:**
|
||||
```cpp {.line-numbers}
|
||||
3 3 2
|
||||
1 2 1
|
||||
2 3 2
|
||||
1 3 1
|
||||
2 1
|
||||
1 3
|
||||
```
|
||||
|
||||
**输出样例:**
|
||||
```cpp {.line-numbers}
|
||||
impossible
|
||||
1
|
||||
```
|
||||
|
||||
### 二、理解和感悟
|
||||
|
||||
1. $Floyd$可以求**多源最短路径**,这是其它算法做不到的。
|
||||

|
||||
|
||||
2. $Floyd$**可以处理负权边,但不能处理负权回路**。
|
||||
|
||||
3. 核心就是初始化+三重循环,注意顺序是$k-i-j$,不能反了!$Floyd$是有**动态规划**思想的算法。
|
||||
|
||||
**原理解析**:
|
||||
$f[k][i][j]$表示$i$和$j$之间可以通过编号为$1..k$的节点的最短路径
|
||||
初值$f[0][i][j]$为原图的邻接矩阵
|
||||
|
||||
* $i$到$j$不经过$k$这个节点: $f[k][i][j]$可以从$f[k-1][i][j]$转移
|
||||
* $i$到$j$经过$k$这个节点: 从$f[k-1][i][k]+f[k-1][k][j]$转移
|
||||
|
||||
即$f[k][i][j]=min(f[k-1][i][j],f[k-1][i][k]+f[k-1][k][j])$
|
||||
|
||||
**然后你就会发现最外层一维空间可以省略,因为$f[k]$只$f[k-1]$与有关。**
|
||||
|
||||
**总结**:
|
||||
一句话,$Floyd$算法的本质是$DP$,而$k$**是$DP$的阶段,因此要写最外面**。
|
||||
|
||||
|
||||
|
||||
### 三、实现代码
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 210;
|
||||
const int INF = 0x3f3f3f3f;
|
||||
|
||||
int n, m, k;
|
||||
int d[N][N];
|
||||
|
||||
// 算法结束后,d[a][b]表示a到b的最短距离
|
||||
void floyd() {
|
||||
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]);
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n >> m >> k;
|
||||
// floyd初始化
|
||||
memset(d, 0x3f, sizeof d); // 任意两点间距离正无穷
|
||||
for (int i = 0; i < N; i++) d[i][i] = 0; // 自己和自己是距离为0的
|
||||
// 读入数据
|
||||
while (m--) {
|
||||
int a, b, c;
|
||||
cin >> a >> b >> c;
|
||||
d[a][b] = min(d[a][b], c); // 保留最短边.(可能有重边,保留最短边)
|
||||
}
|
||||
// 调用floyd
|
||||
floyd();
|
||||
|
||||
// 处理所有询问
|
||||
while (k--) {
|
||||
int a, b;
|
||||
cin >> a >> b;
|
||||
// 由于有负权边存在所以约大过INF/2也很合理
|
||||
if (d[a][b] > INF / 2)
|
||||
puts("impossible");
|
||||
else
|
||||
printf("%d\n", d[a][b]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
67
TangDou/AcWing/Floyd/P4159.cpp
Normal file
67
TangDou/AcWing/Floyd/P4159.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
typedef long long LL;
|
||||
const int MOD = 2009;
|
||||
const int N = 110;
|
||||
|
||||
int g[N][N], a[N][N];
|
||||
int n, t;
|
||||
|
||||
//矩阵乘法
|
||||
void mul(int c[][N], int a[][N], int b[][N]) {
|
||||
int t[N][N] = {0};
|
||||
int M = n * 9;
|
||||
for (int i = 1; i <= M; i++) {
|
||||
for (int j = 1; j <= M; j++)
|
||||
for (int k = 1; k <= M; k++)
|
||||
t[i][j] = (t[i][j] + (LL)(a[i][k] * b[k][j]) % MOD) % MOD;
|
||||
}
|
||||
memcpy(c, t, sizeof t);
|
||||
}
|
||||
|
||||
int main() {
|
||||
// n个节点,t时刻
|
||||
scanf("%d %d", &n, &t);
|
||||
|
||||
for (int i = 1; i <= n; i++) { //原图有n个节点
|
||||
|
||||
/* 1点拆9点
|
||||
1-> 1~9
|
||||
2-> 10~18
|
||||
3-> 19~17
|
||||
...
|
||||
|
||||
拆开的点之间的权值是1
|
||||
*/
|
||||
for (int j = 1; j < 9; j++) // 1点拆9点,注意收尾处是小于号
|
||||
g[(i - 1) * 9 + j][(i - 1) * 9 + j + 1] = 1;
|
||||
|
||||
//从下标1开始读入原图中i号节点与其它各节点间的边权
|
||||
char s[11];
|
||||
scanf("%s", s + 1);
|
||||
|
||||
//遍历一下输入的各点间关系图
|
||||
for (int j = 1; j <= n; j++)
|
||||
//如果i->j 存在边,并且边权 = s[j]
|
||||
//比如 1->8, 边权为3; 则 从3'->64'创建一条边权为1的边
|
||||
if (s[j] > '0') g[(i - 1) * 9 + s[j] - '0'][(j - 1) * 9 + 1] = 1;
|
||||
}
|
||||
|
||||
//复制原始底图
|
||||
memcpy(a, g, sizeof g);
|
||||
|
||||
//因为是复制出来,t次幂就变成了t-1次幂
|
||||
t--;
|
||||
|
||||
//矩阵快速幂
|
||||
while (t) {
|
||||
if (t & 1) mul(g, g, a);
|
||||
mul(a, a, a);
|
||||
t >>= 1;
|
||||
}
|
||||
|
||||
//输出结果
|
||||
printf("%d\n", g[1][(n - 1) * 9 + 1]);
|
||||
return 0;
|
||||
}
|
||||
333
TangDou/AcWing/Floyd/P4159.md
Normal file
333
TangDou/AcWing/Floyd/P4159.md
Normal file
@@ -0,0 +1,333 @@
|
||||
## $P4159$ [$SCOI2009$] 迷路
|
||||
[题目传送门](https://www.luogu.com.cn/problem/P4159)
|
||||
|
||||
|
||||
> **前序知识整理**
|
||||
> 关键词:矩阵+快速幂
|
||||
[P1226 【模板】快速幂||取余运算](https://www.cnblogs.com/littlehb/p/16847009.html)
|
||||
[矩阵乘法](https://www.cnblogs.com/littlehb/p/16037718.html)
|
||||
[P3390 【模板】矩阵快速幂](https://www.cnblogs.com/littlehb/p/16039506.html)
|
||||
[P1939 【模板】矩阵加速(数列)](https://www.luogu.com.cn/problem/P1939)
|
||||
|
||||
|
||||
### 一、题目描述
|
||||
|
||||
**题目背景**
|
||||
$windy$ 在 **有向图** 中迷路了。
|
||||
|
||||
**题目描述**
|
||||
该有向图有 $n$ 个节点,节点从 $1$ 至 $n$ 编号,$windy$ 从节点 $1$ 出发,他必须恰好在 $t$ 时刻到达节点 $n$。
|
||||
|
||||
现在给出该有向图,你能告诉 $windy$ **总共有多少种不同的路径**吗?
|
||||
|
||||
答案对 $2009$ 取模。
|
||||
|
||||
注意:$windy$ 不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。
|
||||
|
||||
**输入格式**
|
||||
第一行包含两个整数,分别代表 $n$ 和 $t$。
|
||||
|
||||
第 $2$ 到第 $(n+1)$ 行,每行一个长度为 $n$ 的字符串,第 $(i + 1)$行的第 $j$ 个字符 $c_{i, j}$是一个数字字符,若为 $0$,则代表节点 $i$ 到节点 $j$ 无边,否则代表节点 $i$ 到节点 $j$ 的边的长度为 $c_{i, j}$。
|
||||
|
||||
**输出格式**
|
||||
输出一行一个整数代表答案对 $2009$ 取模的结果。
|
||||
|
||||
### 二、题目解析
|
||||
|
||||
第一反应:咦?这不是图论吗??? 默默的看了眼$t$的范围($<=1e9$),**死了心**
|
||||
<font color='red' size=4><b>
|
||||
蒟蒻豆爸解释一下:</b></font>为什么看一眼$t$的范围就知道不是图论呢?原因就是这个$t$,一般图论就是用$floyd,bellmanFord,spfa,dijkstra$这些东东,而这些东东的时间复杂度最好也就是$O((n+m)log~m)$,一个$m$就干到了$1e9$,不$TLE$就怪了
|
||||
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
| $floyd$ | $bellmanFord$ | $spfa$ | $dijkstra$ |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| $O(N^3)$ | $O(N\times M)$ | $O(N\times M)$ |$O((n+m)log~m)$|
|
||||
</div>
|
||||
|
||||
|
||||
$DP$!! $DP$一定可以!!!
|
||||
<font color='red' size=4><b>
|
||||
蒟蒻豆爸解释一下:</b></font>$DP$能做到线性复杂度就足够牛$X$了吧,就算是线性的,也一样会$TLE$,为啥呢?因为跑一遍所有边就是$1e9$~
|
||||
|
||||
默默的看了眼$t$的范围,又死了心
|
||||
|
||||
那么怎么做呢?
|
||||
|
||||
#### 一、边权为$1$的有向图中 两点间边权和 恰好是$k$ 的路径条数
|
||||
首先,我们把这道题想简单一点,如果题目中的每一条边都没有边权,只用$1$或$0$来表示两个点之间是否存在边,并且用邻接矩阵来存这张图,那么我们又可以得到些什么呢?
|
||||
|
||||
|
||||
**举个栗子**
|
||||
|
||||
我们以下面这张图为例
|
||||

|
||||
|
||||
以邻接矩阵来表示这个矩阵:
|
||||
$\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}
|
||||
$矩阵$1$
|
||||
|
||||
其中,$a_{ij}$ 表示$i$到$j$之间是否有连线;
|
||||
|
||||
那么,我们把它 **平方** 一下,又可以得到什么呢?(友情提示:如果不清楚矩阵乘法,请[点这里](https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5%E4%B9%98%E6%B3%95/5446029?fr=aladdin))
|
||||
|
||||
$\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}
|
||||
\times
|
||||
\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}=
|
||||
\begin{bmatrix}
|
||||
2& 0 & 1\\
|
||||
1& 1 & 1 \\
|
||||
0& 1 & 1
|
||||
\end{bmatrix}
|
||||
$矩阵$2$
|
||||
|
||||
你又发现了什么呢?
|
||||
|
||||
好的,如果还没发现,我们再来将矩阵$1$ **三次方**一下:
|
||||
|
||||
$\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}
|
||||
\times
|
||||
\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}
|
||||
\times
|
||||
\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix}=
|
||||
\begin{bmatrix}
|
||||
2& 0 & 1\\
|
||||
1& 1 & 1 \\
|
||||
0& 1 & 1
|
||||
\end{bmatrix}
|
||||
\times
|
||||
\begin{bmatrix}
|
||||
0& 1 & 1\\
|
||||
1&0 &1 \\
|
||||
1& 0 & 0
|
||||
\end{bmatrix} =
|
||||
\begin{bmatrix}
|
||||
1& 2 & 2\\
|
||||
2& 1 & 2 \\
|
||||
2& 0 & 1
|
||||
\end{bmatrix}
|
||||
$ 矩阵$3$
|
||||
|
||||
什么,你还没发现吗???
|
||||
|
||||
那么让我来告诉你吧!!!
|
||||
|
||||
矩阵$1$,我们可以把$a_{ij}$ 看成通过 **一条边** ,由$i$到达$j$的情况总数
|
||||
矩阵$2$,我们可以把$a_{ij}$ 看成通过 **两条边** ,由$i$到达$j$的情况总数
|
||||
矩阵$3$,我们可以把$a_{ij}$ 看成通过 **三条边** ,由$i$到达$j$的情况总数
|
||||
|
||||
不信?我们举个栗子:
|
||||
从点$1$到点$1$,且通过 **一条边** 的情况不存在,记为$0$;
|
||||
从点$1$到点$1$,且通过 **两条边** 的情况共两种($1->2->1$ $and$ $1->3->1$),记为$2$;
|
||||
从点$1$到点$1$,且通过 **三条边** 的情况仅有一种($1->2->3->1$),记为$1$;
|
||||
|
||||
再回头看看矩阵吧!!!是不是完全满足这个条件呢???
|
||||
所以我们就可以得出结论啦:
|
||||
|
||||
**在矩阵$A^x$中,$A^x_{ij}$表示由$i$到$j$经过$x$条边的情况总数**
|
||||
|
||||
所以这就可以运用 **快速幂** 啦!!!
|
||||
|
||||
仔细算一下时间复杂度,$O(n*logn)$,稳稳滴!!!
|
||||
|
||||
那么,这道题就可以很快打出来啦——吗?
|
||||
|
||||
显然是不可以的。
|
||||
|
||||
可能你已经发现了,**我们所有的推论都建立在边权为$1$的情况上**,可是这道题目呢?
|
||||
|
||||
> 接下来有 $N$行,每行一个长度为$N$的字符串。第$i$行第$j$列为 $0$表示从节点$i$到节点$j$没有边,为$1$到$9$表示从节点$i$到节点$j$需要耗费的时间。
|
||||
|
||||
呀呀呀,这道题目的边权不只是$1$呀!
|
||||
|
||||
!(⊙ o ⊙)!
|
||||
|
||||
怎么办呢?
|
||||
|
||||
#### 二、边权不为$1$的有向图中 两点间边权和 恰好是$k$ 的路径条数
|
||||
|
||||
虽然我们发现不能直接使用我们的结论,但是最大边权是$9$!$N$也不超过$10$!都不算大!
|
||||
|
||||
那我们就可以采用一种叫做 **拆点** 的方法:把 **一个点拆成$9$个点(本质是按边权拆的)**。
|
||||
|
||||
并且,我们发现即使如此拆点,$N$也不会超过$100$,妥妥的可以呀!
|
||||
|
||||
但怎么拆点呢?
|
||||
|
||||
我们先来试一下拆一个边权不超过$2$的图吧!
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/99615e01cf1347c18b835be2053c87eb.png' width=40%></center>
|
||||
|
||||
可得矩阵
|
||||
|
||||
$$\large \begin{bmatrix}
|
||||
0 & 2 \\
|
||||
2 & 1
|
||||
\end{bmatrix}$$
|
||||
|
||||
将其拆点:
|
||||
<center><img src='https://img-blog.csdnimg.cn/d114eb837c744792bf579cbde9d99755.png' width=40%></center>
|
||||
|
||||
|
||||
可得到新矩阵 :
|
||||
$$\large \begin{bmatrix}
|
||||
0 & 1 & 0 & 0 \\
|
||||
0 & 0 & 1 & 0 \\
|
||||
0 & 0 & 1 & 1 \\
|
||||
1 & 0 & 0 & 0
|
||||
\end{bmatrix}$$
|
||||
|
||||
将其平方:
|
||||
$$\large \begin{bmatrix}
|
||||
0 & 1 & 0 & 0 \\
|
||||
0 & 0 & 1 & 0 \\
|
||||
0 & 0 & 1 & 1 \\
|
||||
1 & 0 & 0 & 0
|
||||
\end{bmatrix}
|
||||
\times
|
||||
\begin{bmatrix}
|
||||
0 & 1 & 0 & 0 \\
|
||||
0 & 0 & 1 & 0 \\
|
||||
0 & 0 & 1 & 1 \\
|
||||
1 & 0 & 0 & 0
|
||||
\end{bmatrix} =
|
||||
\begin{bmatrix}
|
||||
0 & 0 & 1 & 0 \\
|
||||
0 & 0 & 1 & 1 \\
|
||||
1 & 0 & 1 & 1 \\
|
||||
0 & 1 & 0 & 0
|
||||
\end{bmatrix}
|
||||
$$
|
||||
|
||||
**验算一下:**
|
||||
|
||||
原来有点$1$到点$2$并用经过$2$边权的方案总数有一种($1->2$,边权为$2$);
|
||||
|
||||
现在来说,点$1$变为点$1'$,点$2$变为点$3'$,经过$2$边权的方案总数依旧是$2$($1'->2'->3'$,边权均为$1$);
|
||||
|
||||
那么则说明我们的拆点是正确的。
|
||||
|
||||
可以发现这样的话仍然是满足题意的
|
||||
|
||||
**这是为什么呢?为啥要这样拆点呢?**
|
||||
|
||||
<font color='red' size=4><b>因为可以更新答案的那条路径,一定走到了$x_i$号节点,正准备走到原来的另一个节点</b></font>
|
||||
|
||||
不是很好解释,自己多画几张图试试就知道了(我知道你们懒,给你们画一个)
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/a03504f45c664812ab0c2519ec846fcb.png' width=40%></center>
|
||||
|
||||
**拆点** 操作
|
||||
<center><img src='https://img-blog.csdnimg.cn/60bdc2b5775a400186a538b5f6496038.png' width="40%"></center>
|
||||
|
||||
那么既然我们通过拆点操作将所有点之间的边权都变成了$1$,那么我们就可以用刚才得到的 **结论** 啦!!!
|
||||
|
||||
#### 时间复杂度
|
||||
$O(n^3\log ~t)$
|
||||
|
||||
|
||||
#### 实现代码
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
typedef long long LL;
|
||||
const int MOD = 2009;
|
||||
const int N = 110;
|
||||
|
||||
int g[N][N], a[N][N];
|
||||
int n, t;
|
||||
|
||||
//矩阵乘法
|
||||
void mul(int c[][N], int a[][N], int b[][N]) {
|
||||
int t[N][N] = {0};
|
||||
int M = n * 9;
|
||||
for (int i = 1; i <= M; i++) {
|
||||
for (int j = 1; j <= M; j++)
|
||||
for (int k = 1; k <= M; k++)
|
||||
t[i][j] = (t[i][j] + (LL)(a[i][k] * b[k][j]) % MOD) % MOD;
|
||||
}
|
||||
memcpy(c, t, sizeof t);
|
||||
}
|
||||
|
||||
int main() {
|
||||
// n个节点,t时刻
|
||||
scanf("%d %d", &n, &t);
|
||||
|
||||
for (int i = 1; i <= n; i++) { //原图有n个节点
|
||||
|
||||
/* 1点拆9点
|
||||
1-> 1~9
|
||||
2-> 10~18
|
||||
3-> 19~17
|
||||
...
|
||||
|
||||
拆开的点之间的权值是1
|
||||
*/
|
||||
for (int j = 1; j < 9; j++) // 1点拆9点,注意收尾处是小于号
|
||||
g[(i - 1) * 9 + j][(i - 1) * 9 + j + 1] = 1;
|
||||
|
||||
//从下标1开始读入原图中i号节点与其它各节点间的边权
|
||||
char s[11];
|
||||
scanf("%s", s + 1);
|
||||
|
||||
//遍历一下输入的各点间关系图
|
||||
for (int j = 1; j <= n; j++)
|
||||
//如果i->j 存在边,并且边权 = s[j]
|
||||
//比如 1->8, 边权为3; 则 从3'->64'创建一条边权为1的边
|
||||
if (s[j] > '0') g[(i - 1) * 9 + s[j] - '0'][(j - 1) * 9 + 1] = 1;
|
||||
}
|
||||
|
||||
//复制原始底图
|
||||
memcpy(a, g, sizeof g);
|
||||
|
||||
//因为是复制出来,t次幂就变成了t-1次幂
|
||||
t--;
|
||||
|
||||
//矩阵快速幂
|
||||
while (t) {
|
||||
if (t & 1) mul(g, g, a);
|
||||
mul(a, a, a);
|
||||
t >>= 1;
|
||||
}
|
||||
|
||||
//输出结果
|
||||
printf("%d\n", g[1][(n - 1) * 9 + 1]);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
55
TangDou/AcWing/Floyd/UVA125.cpp
Normal file
55
TangDou/AcWing/Floyd/UVA125.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#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;
|
||||
}
|
||||
90
TangDou/AcWing/Floyd/UVA125.md
Normal file
90
TangDou/AcWing/Floyd/UVA125.md
Normal file
@@ -0,0 +1,90 @@
|
||||
## $VUA~125$ $Numbering$ $Paths$
|
||||
|
||||
[题目传送门](https://www.luogu.com.cn/problem/UVA125)
|
||||
|
||||
### 一、题目描述
|
||||
给定一些组数据,每组数据由一个 $n$ 和 $n$ 对整数 $j$,$k$ 组成。由$EOF$停止读入。每组 $j_i,k_i$ 意味着存在一条由点 $j_i$到点 $k_i$的 **单向边**。其中,所有 $j_i,k_i$中的**最大值** $+1$ 即为 最终矩阵边长 $a$。可能存在 **自环**。数据保证任意一个点的入度和出度都不大于 $30$。
|
||||
|
||||
每组输出,包括一个字符串`matrix for city`,一个整数 $t$ 表示从 $0$ 开始的组数,以及一个 $a\times a$ 的矩阵 $M$,其中第 $i$ 行第 $j$ 个元素 $M[i][j]$ 表示从点 $i$ 到 $j$ 的**不同路径数量**。如果有无数条,在该位置输出 $-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$ 时,那么它就是图上环的一部分。
|
||||
|
||||
### 三、实现代码
|
||||
```c++
|
||||
#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;
|
||||
}
|
||||
```
|
||||
23
TangDou/AcWing/Floyd/矩阵快速幂待读资料.md
Normal file
23
TangDou/AcWing/Floyd/矩阵快速幂待读资料.md
Normal file
@@ -0,0 +1,23 @@
|
||||
【算法】数学
|
||||
https://www.cnblogs.com/onioncyc/p/6146143.html
|
||||
|
||||
矩阵快速幂总结
|
||||
https://blog.csdn.net/wust_zzwh/article/details/52058209
|
||||
|
||||
推荐一些题目:
|
||||
|
||||
简单的:
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=1757
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=1575
|
||||
|
||||
不简单的:
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=3483
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=2855
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=3658
|
||||
|
||||
http://acm.hdu.edu.cn/showproblem.php?pid=4565
|
||||
Reference in New Issue
Block a user