传送门:牛客
题目描述:
奶牛们正在找工作。农场主约翰知道后,鼓励奶牛们四处碰碰运气。而且他还加了一条要求:一头牛在一个城市
最多只能赚D(1≤D≤1000)美元,然后它必须到另一座城市工作。当然,它可以在别处工作一阵子后又回到原
来的城市再最多赚D美元。而且这样的往返次数没有限制。
城市间有P(1≤P≤150)条单向路径连接,共有C(2≤C≤220)座城市,编号从1到C。奶牛贝茜当前处在城市S
(1≤S≤C)。路径i从城市A_i到城市B_i(1≤A_i≤C,1≤B_i≤C),在路径上行走不用任何花费。
为了帮助贝茜,约翰让它使用他的私人飞机服务。这项服务有F条(1≤F≤350)单向航线,每条航线是从城市J_i
飞到另一座城市K_i(1≤J_i≤C,1≤K_i≤C),费用是T_i(1≤T_i≤50000)美元。如果贝茜手中没有现钱,可以
用以后赚的钱来付机票钱。
贝茜可以选择在任何时候,在任何城市退休。如果在工作时间上不做限制,贝茜总共可以赚多少钱呢?如果赚的
钱也不会出现限制,就输出-1。
输入:
100 3 5 2 1
1 5
2 3
1 4
5 2 150
2 5 120
输出:
250
一道求最长路的题目,需要好好掌握
关于最长路:如果边权全是正数或者全是负数,可以使用
d
i
j
k
s
t
r
a
dijkstra
dijkstra解决,但是如果边权有正有负,那么
d
i
j
k
s
t
r
a
dijkstra
dijkstra将无法解决,此时只能寻求于
S
p
f
a
Spfa
Spfa
主要思路:
- 首先我们观察一下题意,我们发现有两种路线,一种是没有路费的,一种是有路费的.并且我们到达一座城市就可以获得D,那么我们为什么不把我们的收获加入到我们的边权之中去呢.也就是如果不使用机票,我们经过这条边就可以获得D,如果使用机票,我们就可以获得D-Ti.那么这道题最终就转化成了一道跑最长路的问题,最终的答案就是图上最长的一段路径,并且显然的,我们这张图是有正有负的,所以并不能使用 d i j k s t r a dijkstra dijkstra进行,得使用 S p f a Spfa Spfa了
- 对于无限钱的问题,那就是有一个正环存在了,因为有一个正环存在,那么我们显然可以在正环中无限循环,因为始终是正收入,所以我们还需要Spfa来判环
对于Spfa跑最长路的问题,网上博客讲的都很清楚,但是大多遇到判环问题都是一笔带过,但其实判环这一环节还是需要好好品味一下的,接下来就较为详细的介绍一下判环的依据
Spfa有两种判环的方式:
一种是判断以当前点结尾的最长路的边是否大于
n
−
1
n-1
n−1条,如果大于就是有环的存在
这种方法比较好理解,因为我们的点最多只有n个,那么对于我们的最长路来说,如果没有环的问题的话,我们的路径数最多肯定等于点数-1,也就是一条路直接过去(如果不理解,可以自己画一下)关于更新部分,也非常好理解,当前的点既然能被更新,那么就说明目前这种状态是最长路,那么显然边数应该由他之前的点的最长路边数递增过来
部分代码:
下面中的cnt记录的是边数
for(int i=0;i<edge[u].size();i++) {
int v=edge[u][i].v;
if(dis[v]<dis[u]+edge[u][i].w) {
dis[v]=dis[u]+edge[u][i].w;
cnt[v]=cnt[u]+1;
if(cnt[v]>c-1) return -1;
if(!vis[v]) {
q.push(v);
vis[v]=1;
}
}
}
另外一种是判断当前的点的更新次数是否大于n-1
这一种方法也是网上使用的较多的方式,这种方式主要是利用了BFS的性质.因为是Spfa的本质还是BFS.所以当Spfa中每次扩展一个点的时候.注意这个扩展指的是入队而不是松弛,有很多博客写的是松弛部分,这是错误的,因为我们在一次BFS中可能有多个相等数量边优化该点的操作,但是此时我们的BFS扩展的边数是相同的.类似于下面的4点
我们发现此时以该点为终点的最长路的边肯定是大于等于该点入队的次数的(因为即使我们每一个BFS多扩展一条边都更新了该点,该点入队的次数也不会大于边数),那么假设我们当前的点更新次数都比n-1大了,那就意味着我们的边数大于了n-1,那肯定存在环.
部分代码:
if(dis[u]+w<dis[v]){
dis[v]=dis[u]+w;
if(!vis[v]){
if(++cnt[v]>=n)//注意就是这个位置的判断。一定要保证在判vis之后,即判入队次数;而不是在判vis之前,即判松弛次数!!!
{
printf("YES\n");return;
}
vis[v]=1;q.push(v);
}
}
但是,第二种方法的空间复杂度显然比第一种要大,时间复杂度也比第一种要大,理由这里就不讲了,可以自行百度或者思考,所以强烈建议使用第一种方法,毕竟第一种方法既好用,又好理解,为什么不用呢??
下面是这道题的代码部分(使用第一种方法判环):
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int d,p,c,f,s;
struct Node{
int v,w;
};
vector<Node>edge[maxn];
int vis[maxn],cnt[maxn],dis[maxn];
int spfa(int S) {
queue<int>q;
for(int i=1;i<=c;i++) dis[i]=-inf;
dis[S]=d;
vis[S]=1;cnt[S]=0;
q.push(S);
while(!q.empty()) {
int u=q.front();q.pop();
vis[u]=0;
for(int i=0;i<edge[u].size();i++) {
int v=edge[u][i].v;
if(dis[v]<dis[u]+edge[u][i].w) {
dis[v]=dis[u]+edge[u][i].w;
cnt[v]=cnt[u]+1;
if(cnt[v]>c-1) return -1;
if(!vis[v]) {
q.push(v);
vis[v]=1;
}
}
}
}
return 1;
}
int main() {
d=read(),p=read(),c=read(),f=read(),s=read();
for(int i=1;i<=p;i++) {
int u=read(),v=read();
edge[u].push_back({v,d});
}
for(int i=1;i<=f;i++) {
int u=read(),v=read(),w=read();
edge[u].push_back({v,d-w});
}
if(spfa(s)==-1) printf("-1");
else {
int ans=-inf;
for(int i=1;i<=c;i++) ans=max(ans,dis[i]);
cout<<ans<<endl;
}
return 0;
}