1261:【例9.5】城市交通路网
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 5767 通过数: 4131
【题目描述】
下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
如图:求v1到v10的最短路径长度及最短路径。
【输入】
第一行为城市的数量N;
后面是N*N的表示两个城市间费用组成的矩阵。
【输出】
A->E的最省费用。
【输入样例】
10
0 2 5 1 0 0 0 0 0 0
0 0 0 0 12 14 0 0 0 0
0 0 0 0 6 10 4 0 0 0
0 0 0 0 13 12 11 0 0 0
0 0 0 0 0 0 0 3 9 0
0 0 0 0 0 0 0 6 5 0
0 0 0 0 0 0 0 0 10 0
0 0 0 0 0 0 0 0 0 5
0 0 0 0 0 0 0 0 0 2
0 0 0 0 0 0 0 0 0 0
【输出样例】
minlong=19
1 3 5 8 10
【题意】:看题后就发现这个左图是没有用的,只看右图就好,给你城市之间的关系图,每条连线上面都有一个花费(就称为过路费),从城市1走到城市2过路费为2,从城市2走到城市5过路费为12,那么从1到2再到5的过路费是2+12=14。有了这些关系,让你求出从1走到10的过路费最小为多少,因为从1到10有好多条路,最终求出花费最少的,并且将最小花费的路径打印出来。注意是输入一个n,是代表让你求1到n的最小花费。
【样例】:首先这道题是用邻接矩阵(二维数组实现)来存每个城市之间的关系,首先第一行第二列有值则代表从城市1能够直接到达城市2,这个值是2则代表从城市1到城市2需要花的过路费是2,第一行第四列是0则代表从城市1不能直接到达城市4。(虽然这是一道最短路的题,其实也都是动态规划的思想)
【思路】:这道题和挖地雷比较像,做法也大致相同。
1.正推的方法:首先把从1到n的最小花费拆成从1到n-1这个子问题,再把1到n-1拆成1到n-2,,,直到拆成1到2这个子问题。要求到达2城市的最小花费,就看哪个城市能到达2,这里1能到达2,那么 到达2的最小花费=到达1的最小花费+从1到2的过路费,没有其他路能到达2了,初始位置就在城市1,则代表到达1的花费为0,那么到达2的最小花费就更新为0+2=2,我们这里用dp[i]数组表示到达 i 城市的最小花费,用mp[x][y]表示从城市x到达城市y需要花多少过路费(也就是所输入的n*n矩阵),则dp[1]=0,因为求最小花费,dp数组越更新越小,表明需要将dp数组初始化为很大的数。那么我们来模拟下dp[i]的更新过程。
初始化dp[i] = 1e9;
从城市1出发:dp[1]=0;
城市2:1能到达2,dp[2]>dp[1]+mp[1][2]成立,则dp[2]=dp[1]+mp[1][2]=0+2=2;由于是单向图则没有其他城市能到达2了,看哪个城市能够到达2,观察mp数组第2列就可以了。
dp[2] = 2;最短路径是1-2
城市3:1能到达3,dp[3]>dp[1]+mp[1][3]成立,则dp[3]=dp[1]+mp[1][3]=0+5=5;从1到达城市3的最小花费是5
dp[3] = 5;最短路径是1-3
城市4:1能到达4,dp[4]>dp[1]+mp[1][4]成立,则dp[4]=dp[1]+mp[1][4]=0+1=1;从1到达城市4的最小花费是1
dp[4] = 1;最短路径是1-4
城市5:2能到达5,dp[5]>dp[2]+mp[2][5]成立,则dp[5]=dp[2]+mp[2][5]=2+12=14;从1到5目前最小花费是14,先从1到2,再从2到5
3能到达5,dp[5]>dp[3]+mp[3][5]成立,则dp[5]=dp[3]+mp[3][5]=5+6=11;从1到5目前最小花费是11,先从1到3,再从3到5
4能到达5,dp[5]>dp[4]+mp[4][5]不成立,则不更新dp[5],
dp[5] = 11;最短路径是1-3-5
城市6:2能到达6,dp[6]>dp[2]+mp[2][6]成立,则dp[6]=dp[2]+mp[2][6]=2+14=16;从1到6目前最小花费是16,先从1到2,再从2到6
3能到达6,dp[6]>dp[3]+mp[3][6]成立,则dp[6]=dp[3]+mp[3][6]=5+10=15;从1到6目前最小花费是15,先从1到3,再从3到6
4能到达6,dp[6]>dp[4]+mp[4][6]成立,则dp[6]=dp[4]+mp[4][6]=1+12=13;从1到6目前最小花费是13,先从1到4,再从4到6
dp[6] = 13;最短路径是1-4-6
城市7:3能到达7,dp[7]>dp[3]+mp[3][7]成立,则dp[7]=dp[3]+mp[3][7]=5+4=9;从1到7目前最小花费是9,先从1到3,再从3到7
4能到达7,dp[7]>dp[4]+mp[4][7]不成立,则不更新dp[7]
dp[7] = 9;最短路径是1-3-7
城市8:5能到达8,dp[8]>dp[5]+mp[5][8]成立,则dp[8]=dp[5]+mp[5][8]=11+3=14;从1到8目前最小花费是11,先从1到3,再从3到5,再从5到8
6能到达8,dp[8]>dp[6]+mp[6][8]不成立,则不更新dp[8]
dp[8] = 14;最短路径是1-3-5-8
城市9:5能到达9,dp[9]>dp[5]+mp[5][9]成立,则dp[9]=dp[5]+mp[5][9]=11+9=20;从1到9目前最小花费是20,先从1到3,再从3到5,再从5到9
6能到达9,dp[9]>dp[6]+mp[6][9]成立,则dp[9]=dp[6]+mp[6][9]=13+5=18;从1到9目前最小花费是18,先从1到4,再从4到6,再从6到9
7能到达9,dp[9]>dp[7]+mp[7][9]不成立,则不更新dp[9]
dp[9] = 18;最短路径是1-4-6-9
城市10:8能到达10,dp[10]>dp[8]+mp[8][10]成立,则dp[10]=dp[8]+mp[8][10]=14+5=19;从1到10目前最小花费是11,先从1到3,再从3到5,再从5到8,再从8到10
9能到达10,dp[10]>dp[9]+mp[9][10]不成立,则不更新dp[10]
dp[8] = 19;最短路径是1-3-5-8-10
其他样例按此类推,最终求出dp[n]的值,则表示从1到城市n的最小花费是dp[n]。
打印路径:用一个新数组p[i]来保存当前 i 城市是从哪个城市走过来的,从n开始,递归打印p[n]。
2. 如果是逆推法,则从n开始往前更新,dp[i]此时则表示从n到达i位置的最小花费,dp[1]则为从n到1的最小花费。
打印路径:用一个新数组p[i]来保存当前 i 城市是从哪个城市走过来的,因为是逆推,所以从1开始,循环打印即可。
正推 递归打印路径
#include <iostream>
using namespace std;
const int maxn = 1e9;
int mp[10001][10001], dp[10001], p[10001];
void print(int k) {
if (k == 0) return;
print(p[k]);
cout << k << " ";
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
dp[i] = maxn;
for (int j = 1; j <= n; j++) {
cin >> mp[i][j];
}
}
dp[1] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (mp[j][i] && dp[i] > dp[j] + mp[j][i] ) {
dp[i] = dp[j] + mp[j][i];
p[i] = j;
}
}
}
cout << "minlong=" << dp[n] << endl;
print(n);
return 0;
}
逆推法 循环打印路径
#include <iostream>
using namespace std;
int mp[205][205], p[205], dp[205];
int main()
{
int n, a[205], maxn = 0, k;
cin >> n;
for (int i = 1; i <= n; i++) {
dp[i] = 100000000;
for (int j = 1; j <= n; j++) {
cin >> mp[i][j];
}
}
dp[n] = 0;
for (int i = n; i >= 1; i--) {
for (int j = i + 1; j <= n; j++) {
if (mp[i][j] && dp[i] > dp[j] + mp[i][j]) {
dp[i] = dp[j] + mp[i][j];
p[i] = j;
}
}
}
cout << "minlong=" << dp[1] << endl;
k = 1;
while (k) {
cout << k << " ";
k = p[k];
}
cout << endl;
return 0;
}
有疑问欢迎交流