http://poj.org/problem?id=2457
题意:m个关系n个货物,关系中的a b代表可以拿货物a换取货物b,构造一条最短路,起点1终点n,输出交换次数和路径。
思路:看了这个教程。注意是有向图。dijkstra和spfa事实上是一个思路,都是找到最短路后,沿着这条路根据找路时存储的前驱来存储相应的节点。记录路径的方式也有几种,刚开始用数组记录,绕的我头都晕了。后来用栈优化,一目了然。
这题后来也回顾过,好好想了想初始化问题。dijkstra和spfa其实都可以一开始就将dis数组置为无穷大,pre数组更新为-1。dijkstra中仅仅把dis[1]置为0,但是未访问,这样所有的点都能算上。spfa中第一个点要入队列,dis[1]置为0,变成已访问,同样可以算上所有的点。
这样初始化就不会对起点进行一些对pre和dis的繁琐操作,使思路更加清晰。
首先来终极简化版本,以后做路径题方式就是以这个为参考:
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int N = 1005;
const int INF = 0x3f3f3f3f;
int dis[N], G[N][N], pre[N], ans[N], n;
bool vis[N];
void dijkstra(int s)
{
memset(vis, false, sizeof(vis));
memset(pre, -1, sizeof(pre));
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
for(int i = 1; i <= n; i++)
{
int k = -1;
for(int j = 1; j <= n; j++)
{
if(!vis[j] && (k==-1 || dis[j]<dis[k]))
k = j;
}
if(k == -1) break;//已经遍历所有的点
vis[k] = true;
for(int j = 1; j <= n; j++)
{
if(!vis[j] && dis[k]+G[k][j]<dis[j])
{
dis[j] = dis[k]+G[k][j];
pre[j] = k;
}
}
}
}
void spfa(int s)
{
memset(vis, false, sizeof(vis));
memset(pre, -1, sizeof(pre));
queue<int>que;
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
vis[s] = true;
que.push(s);
while(!que.empty())
{
int now = que.front();
que.pop();
vis[now] = false;
for(int i = 1; i <= n; i++)
{
if(dis[now]+G[now][i]<dis[i])
{
dis[i] = dis[now]+G[now][i];
pre[i] = now;
if(!vis[i])
{
vis[i] = true;
que.push(i);
}
}
}
}
}
void Print()
{
stack<int>path;
int now = n, cnt = 0;
if(dis[n] == INF) printf("-1\n");
else
{
while(now != -1)
{
path.push(now);
now = pre[now];
cnt++;
}
printf("%d\n", cnt);
while(!path.empty())
{
printf("%d\n", path.top());
path.pop();
}
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int m, s, e;
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(i == j) G[i][j] = 0;
else G[i][j] = INF;
}
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &s, &e);
G[s][e] = 1;
}
// dijkstra(1);
spfa(1);
Print();
return 0;
}
然后是这个把我绕晕了的数组记录路径:
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int N = 1005;
const int INF = 0x3f3f3f3f;
int dis[N], G[N][N], pre[N], ans[N], n;
bool vis[N];
void dijkstra(int s)
{
memset(vis, false, sizeof(vis));
memset(pre, -1, sizeof(pre));
for(int i = 1; i <= n; i++)
{
dis[i] = G[s][i];
if(dis[i]!=INF && i!=s) pre[i] = s;
}
dis[s] = 0;
vis[s] = true;
for(int i = 1; i <= n; i++)
{
int k = -1;
for(int j = 1; j <= n; j++)
{
if(!vis[j] && (k==-1 || dis[j]<dis[k]))
k = j;
}
if(k == -1) break;//已经遍历所有的点
vis[k] = true;
for(int j = 1; j <= n; j++)
{
if(!vis[j] && dis[k]+G[k][j]<dis[j])
{
dis[j] = dis[k]+G[k][j];
pre[j] = k;
}
}
}
}
void spfa(int s)
{
memset(vis, 0, sizeof(vis));
memset(pre, -1, sizeof(pre));
queue<int>que;
for(int i = 1; i <= n; i++)
dis[i] = INF;//初值记得赋无穷,而不是G数组中的值!
dis[s] = 0;
vis[s] = true;
que.push(s);
while(!que.empty())
{
int now = que.front();
que.pop();
vis[now] = false;//记得还原!!
for(int i = 1; i <= n; i++)
{
if(dis[now]+G[now][i]<dis[i])
{
dis[i] = dis[now]+G[now][i];
pre[i] = now;
if(!vis[i])
{
vis[i] = true;
que.push(i);
}
}
}
}
}
void print()
{
memset(ans, 0, sizeof(ans));
int cnt = 0;
if(dis[n] == INF)
printf("-1\n");
else
{
ans[cnt++] = n;
int tmp = pre[n];
while(tmp != -1)
{
ans[cnt++] = tmp;
tmp = pre[tmp];
}
printf("%d\n", cnt);
for(int i = cnt-1; i >= 0; i--)
printf("%d\n", ans[i]);
// printf("sad");
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int m, s, e;
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(i == j) G[i][j] = 0;
else G[i][j] = INF;
}
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &s, &e);
G[s][e] = 1;
}
// dijkstra(1);
spfa(1);
print();
return 0;
}
后来用两种floyd敲了两次,都是TLE。大体思路就是二维数组在DP的时候已经将路径的前驱保存了下来,我们正好利用了这种特殊的性质。记录前驱就和前面思想一样,记录后继很方便,就是floyd的特权吧,然而复杂度太高,人家不让过哎= =
floyd记录前驱的路径记录法:
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 1005;
const int INF = 0x3f3f3f3f;
int G[N][N], pre[N][N], ans[N], n;
void floyd()
{
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(G[i][k]+G[k][j] < G[i][j])
{
G[i][j] = G[i][k]+G[k][j];
pre[i][j] = pre[k][j];
}
}
pre[1][1] = -1;
}
void print()
{
memset(ans, 0, sizeof(ans));
int cnt = 0;
if(G[1][n] == INF)
printf("-1\n");
else
{
ans[cnt++] = n;
int tmp = pre[1][n];
while(tmp != -1)
{
ans[cnt++] = tmp;
tmp = pre[1][tmp];
}
printf("%d\n", cnt);
for(int i = cnt-1; i >= 0; i--)
printf("%d\n", ans[i]);
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int m, s, e;
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(i == j) G[i][j] = 0;
else G[i][j] = INF;
pre[i][j] = i;
}
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &s, &e);
G[s][e] = 1;
}
floyd();
print();
return 0;
}
floyd记录后继的路径记录法:
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 1005;
const int INF = 0x3f3f3f3f;
int G[N][N], pre[N][N], n;
void floyd()
{
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(G[i][k]+G[k][j] < G[i][j])
{
G[i][j] = G[i][k]+G[k][j];
pre[i][j] = pre[i][k];
}
}
pre[n][n] = -1;
}
void print()
{
int cnt = 0;
if(G[1][n] == INF)
printf("-1\n");
else
{
printf("%d\n", G[1][n]+1);
int tmp = pre[1][n];
printf("%d\n", 1);
while(tmp != -1)
{
printf("%d\n", tmp);
tmp = pre[tmp][n];
}
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int m, s, e;
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(i == j) G[i][j] = 0;
else G[i][j] = INF;
pre[i][j] = j;
}
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &s, &e);
G[s][e] = 1;
}
floyd();
print();
return 0;
}