POJ-2353 Ministry 最短路 Or 动态规划
题意:给定一个矩阵,每个元素代表了一个开销,每个位置只能够由上,左,右这些位置传递过来,问从第一行走到最后一行的最少代价为多少.
解法:
1.最短路
若是用最短路来解这一题,我们需要进行构边,同层相邻的节点连双向边,不同层从上往下连单向边,最后设定一个超级源点和超级终点即可.
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <queue> #include <iostream> #include <algorithm> #include <vector> #define SS 51009 #define TT 51010 // 从1开始计算行,所以SS,TT的取值要谨慎 using namespace std; int N, M, idx, cost[105][505]; int head[105*505], path[105*505]; long long dis[105*505]; bool vis[105*505]; struct Edge { int x, fee, next; }e[250000]; void addedge(int a, int b, int fee) { ++idx; e[idx].x = b, e[idx].fee = fee; e[idx].next = head[a]; head[a] = idx; } struct cmp { bool operator() (int a, int b) { return dis[a] > dis[b]; } }; void dijkstra() { priority_queue<int, vector<int>, cmp>q; int pos; memset(dis, 0x3f, sizeof (dis)); memset(vis, 0, sizeof (vis)); dis[SS] = 0; q.push(SS); while (!q.empty()) { pos = q.top(); q.pop(); vis[pos] = true; if (pos == TT) { return; } for (int i = head[pos]; i != -1; i = e[i].next) { if (!vis[e[i].x] && dis[e[i].x] > dis[pos] + e[i].fee) { dis[e[i].x] = dis[pos] + e[i].fee; path[e[i].x] = pos; q.push(e[i].x); } } } } void output(int x) { if (path[x]) { output(path[x]); } if (x != SS && x != TT) { if (x % N != 0) printf("%d\n", x-N*(x/N)); else printf("%d\n", N); } } int main() { while (scanf("%d %d", &M, &N) == 2) { idx = -1; memset(head, 0xff, sizeof (head)); for (int i = 1; i <= M; ++i) { for (int j = 1; j <= N; ++j) { scanf("%d", &cost[i][j]); } } for (int i = 1; i <= M; ++i) { for (int j = 2; j <= N; ++j) { // 先处理同层边 addedge(i*N+j-1, i*N+j, cost[i][j]); // 将二维的点坐标用一维数字表示 addedge(i*N+j, i*N+j-1, cost[i][j-1]); } } for (int i = 2; i <= M; ++i) { for (int j = 1; j <= N; ++j) { addedge((i-1)*N+j, i*N+j, cost[i][j]); // 不同层的边只能够是 } } // 建立超级源点以及超级终点 for (int j = 1; j <= N; ++j) { addedge(SS, N+j, cost[1][j]); addedge(M*N+j, TT, 0); } dijkstra(); output(TT); } return 0; }
2.动态规划
这题还有一种写法就是通过动态规划,每个点都是由上,左,右三个方向推过来,按理说同一行的相邻的点会由于先后关系不确定而无法实现状态的转移
但是这里有一个较好的性质就是从各个方向进行状态转移是独立的,也就是说任何一个向左的转移都与同层间向右的转移无关,向右,向下同理. 在最终
结果的体现上就是在同一行中要么是过单点,即向下转移,要么是一条往左的路,要么是一条往右的路,不会在一层中先往左(右),后往右(左).
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int M, N, cost[105][505]; long long dp[105][505]; int path[105*505]; int DP() { for (int i = 1; i <= N; ++i) { dp[1][i] = cost[1][i]; // 第一行的开销我们可以直接给出 path[N+i] = 0; // 这个赋初值很重要,因为该组数据的第一行,可能是上一组数据的第二或者是其他行,因此不能依赖于全局变量的0 } for (int i = 2; i <= M; ++i) { for (int j = 1; j <= N; ++j) { // 从上到下 dp[i][j] = dp[i-1][j] + cost[i][j]; path[i*N+j] = (i-1)*N+j; // 这同样是一个初始化的过程 } for (int j = 2; j <= N; ++j) { // 从左到右 if (dp[i][j] > dp[i][j-1] + cost[i][j]) { dp[i][j] = dp[i][j-1] + cost[i][j]; path[i*N+j] = i*N+j-1; } } for (int j = N-1; j >= 1; --j) { // 从右到左 if (dp[i][j] > dp[i][j+1] + cost[i][j]) { dp[i][j] = dp[i][j+1] + cost[i][j]; path[i*N+j] = i*N+j+1; } } } int Min = dp[M][1], end = 1; for (int i = 2; i <= N; ++i) { if (Min > dp[M][i]) { Min = dp[M][i]; end = i; } } return M*N+end; } void output(int x) { if (path[x]) { output(path[x]); } if (x % N != 0) { printf("%d\n", x-N*(x/N)); } else { printf("%d\n", N); } } int main() { int pos; while (scanf("%d %d", &M, &N) == 2) { for (int i = 1; i <= M; ++i) { for (int j = 1; j <= N; ++j) { scanf("%d", &cost[i][j]); } } pos = DP(); output(pos); } return 0; }