题意
单向无环无重边图,每条弧都需花费一段时间,起点为1,终点为n,求在时间T内最多经过多少个点?(节点从1到n,n <= 5000,共有m条弧,m <=5000, T<= 5000)
思路
可以在最短路同时,维护目前的路径长度,也即原始版本最短路为dp[i],代表从起点1到i所经过的最短路径,现在则维护dp[i][j],代表从起点1到i经过j个点的最短路径
注意
- 起点必须为1,所以spfa的queue最初只需要加入起点1
- 因为起点为1,所以对于点i来说,dp[i]不是严格递增的,因为有些j小于能到达的步数,此时dp[i][j]为初始化的inf
Code
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include <assert.h>
#include <sstream>
#include <cctype>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int maxn = 5e3 + 3;
const int maxm = 5e3 + 3;
int first[maxn];
int nxt[maxn];
int to[maxn];
int cost[maxn];
int len;
int dp[maxn][maxn];
int pre[maxn][maxn];
bool vis[maxn][maxn];
int st[maxn];
void addedge(int u,int v,int c){
nxt[len] = first[u];
to[len] = v;
cost[len] = c;
first[u] = len++;
}
int main(){
freopen("input.txt","r",stdin);
int n,m,t;
memset(first,-1,sizeof first);
memset(dp,0x6f,sizeof dp);
scanf("%d%d%d",&n,&m,&t);
for(int i = 0;i < m;i++){
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
addedge(u,v,c);
}
for(int i = 1;i <= n;i++)dp[i][1] = 0;
queue<P>que;
que.push(P(1,1));
vis[1][1] = true;
while(!que.empty()){
int u = que.front().first;int num = que.front().second;que.pop();vis[u][num] = false;
if(u == n)continue;
for(int p = first[u];p != -1;p = nxt[p]){
int v = to[p];
if(dp[u][num] + cost[p] <= t && dp[u][num] + cost[p] < dp[v][num + 1]){
dp[v][num + 1] = dp[u][num] + cost[p];
pre[v][num + 1] = u;
if(!vis[v][num]){
que.push(P(v,num + 1));
// printf("%d to %d
",u,v);
vis[v][num] = true;
}
}
}
}
int ans = 1;
for(int i = n;i >= 1;i--){
if(dp[n][i ] <= t){
ans = i;
for(int j = i, u = n;j > 0;j--){
st[j] = u;
u = pre[u][j];
}
break;
}
}
printf("%d
",ans);
for(int i = 1;i <= ans;i++){
printf("%d%c",st[i],i == ans?'
':' ');
}
return 0;
}
学习部分
Code #1 shortest size
#include <bits/stdc++.h>
using namespace std;
int N, M, T, e[5555][3], d[5555][5555], p[5555][5555], a, i, j, x, y, z;
stack<int> s;
int main() {
for (cin >> N >> M >> T; i < M * 3; i++) cin >> e[i / 3][i % 3];
for (i = 0; i <= N; i++) for (j = 0; j <= N; j++) d[i][j] = 1.1e9;
for (d[1][1] = 0, i = 2; i <= N; i++){
bool fl = false;
for (j = 0; j < M; j++){
if (x = e[j][0], y = e[j][1], z = e[j][2], d[i - 1][x] + z < d[i][y] && T - d[i - 1][x] >= z){
d[i][y] = d[i - 1][x] + z, p[i][y] = x;
fl = true;
if(y == N)a = i;
}
}
if(!fl)break;
}
cout << a << '
';
for (j = N; j; j = p[a--][j]) s.push(j);
while (s.size()) cout << s.top() << " ", s.pop();
}
Code #1:Tips
- 由于z + d[i - 1][x] <= T 可能超过int范围,所以可以用 T - d[i - 1][x] >= z代替,也即判断中加法可能超过范围时可以在不等式两边做减法
- 每组输入数据数量一定时,可以使用总index来输入遍历各组输入,不过可读性较差
c++ for (cin >> N >> M >> T; i < M * 3; i++) cin >> e[i / 3][i % 3];
- 逗号操作符返回最右边的数值,
c++ if (x = e[j][0], y = e[j][1], z = e[j][2], d[i - 1][x] + z < d[i][y] && T - d[i - 1][x] >= z){
- 思路不同处
- dp(d)第一维为路径长度,从动态规划方面考虑,次数固定,不需要spfa算法
- 方便跳出循环节约时间,
- 且路径长度往往较短,比起使用节点作为第一维更优
Code#2 :shortest time
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<utility>
#include<vector>
#include<map>
using namespace std;
const int N = 5008;
#define ll long long
#define mp make_pair
#define X first
#define Y second
int n, m, tim, flag;
int minx(int x, int y) { if (x<y)y = x; return y; }
int maxx(int x, int y) { if (x>y)y = x; return y; }
vector< pair<int, int> >a[N];
map<int, int>b[N], c[N];
int d[N];
void yun(int x)
{
if (d[x]) return;
d[x] = 1;
if (x == n) return;
int i, p, p1, p2;
map<int, int>::iterator k;
for (i = 0; i<(int)a[x].size(); i++)
{
yun(p = a[x][i].X);
for (k = b[p].begin(); k != b[p].end(); k++)
{
p1 = (*k).X; p2 = (*k).Y;
if ((p2 + a[x][i].Y) <= tim && (b[x].find(p1 + 1) == b[x].end() || b[x][p1 + 1]>p2 + a[x][i].Y))
{
b[x][p1 + 1] = p2 + a[x][i].Y;
c[x][p1 + 1] = p;
}
}
}
return;
}
void dfs(int x, int y)
{
if (flag) printf(" ");
flag = 1;
printf("%d", x);
if (x < n) dfs(c[x][y], y - 1);
}
int main(void)
{
int i, j, p1, p2, p3;
scanf("%d%d%d", &n, &m, &tim);
for (i = 1; i <= m; i++)
{
scanf("%d%d%d", &p1, &p2, &p3);
a[p1].push_back(mp(p2, p3));
}
b[n][1] = 0;
yun(1);
map<int, int>::iterator k;
j = 0;
for (k = b[1].begin(); k != b[1].end(); k++)
{
j = maxx(j, (*k).X);
}
printf("%d
", j);
flag = 0;
dfs(1, j);
return 0;
}
Code#2:Tips
- 在单向无环图中,dfs比最短路和dp更优
- 由于步数的区间并不连续,代码原作者选择了使用map
- stl的使用
- map
::iterator k; - 在此场景(一个点联通的其它点数目不会很多)下map的效率很高
- (b[x].find(p1 + 1) == b[x].end() || b[x][p1 + 1]>p2 + a[x][i].Y)
- 可以使用find查找key值对应的value,找不到返回end()
- map
Code #2:改编非map版
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include <assert.h>
#include <sstream>
#include <cctype>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int maxn = 5e3 + 3;
const int maxm = 5e3 + 3;
int first[maxn];
int nxt[maxn];
int to[maxn];
int cost[maxn];
int len;
int dp[maxn][maxn];
int pre[maxn][maxn];
int d[maxn];
int st[maxn];
int n,m,t;
void addedge(int u,int v,int c){
nxt[len] = first[u];
to[len] = v;
cost[len] = c;
first[u] = len++;
}
void dfs(int u){
if(d[u] != 0)return;
for(int p = first[u];p != -1;p = nxt[p]){
int v = to[p];
if(d[v] == 0)dfs(v);
if(d[v] == -1)continue;
for(int num = d[v];num <= n;num++){
if(t - dp[v][num] >= cost[p] && dp[v][num] + cost[p] < dp[u][num + 1]){
dp[u][num + 1] = dp[v][num] + cost[p];
pre[u][num + 1] = v;
if(d[u] == 0)d[u] = num + 1;
}
}
}
if(d[u] == 0)d[u] = -1;
}
int main(){
freopen("input.txt","r",stdin);
memset(first,-1,sizeof first);
memset(dp,0x6f,sizeof dp);
scanf("%d%d%d",&n,&m,&t);
for(int i = 0;i < m;i++){
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
addedge(u,v,c);
}
dp[n][1] = 0;
d[n] = 1;
dfs(1);
for(int i = n;i >= 1;i--){
if(dp[1][i] <= t){
printf("%d
",i);
for(int j = 1,k = i;k > 0;j = pre[j][k],k--){
printf("%d%c",j,k == 1?'
':' ');
}
break;
}
}
return 0;
}