这题有三种做法,
第一种,也即是最容易理解的就是dfs+bfs.
刚开始写的时候,dfs那里刚开始时直接处理了sum,让sum+a[tem]。
然后一直调BUG不知道哪里错了,后来发现后面的俩个if语句处理的是
有正环的情况和非环的情况,而这题可能会有负环,提前加了sum,显然是不对的。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<map>
#include<cctype>
#include<cmath>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define LL long long
using namespace std;
const int N=120;
int n;
int a[N];
vector<int> v[N];
int flag[N];
int vis[N];
int fla;
void bfs(int pos)
{
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(pos);
vis[pos]=1;
while(!q.empty())
{
int d=q.front();
q.pop();
if(d==n-1)
{
fla=1;
return;
}
for(int i=0;i<v[d].size();i++)
{
int m=v[d][i];
if(!vis[m])
{
q.push(m);
vis[m]=1;
}
}
}
}
void dfs(int x,int sum)
{
if(sum<=0) return;
if(sum>0&&x==n-1) {fla=1;return;}
for(int i=0;i<(int)v[x].size();i++)
{
int tem=v[x][i];
sum=sum+a[tem];
if(flag[tem]&&sum>flag[tem])
{
bfs(x);
if(fla) return;
}
if(!flag[tem])
{
flag[tem]=sum;
dfs(tem,sum);
}
}
}
int main()
{
while(cin>>n&&n!=-1)
{
fla=0;
memset(flag,0,sizeof(flag));
for(int i=0;i<n;i++)
{
v[i].clear();
cin>>a[i];
int m;
cin>>m;
for(int j=0;j<m;j++)
{
int val;
cin>>val;
v[i].push_back(val-1);
}
}
flag[0]=100;
dfs(0,100);
if(fla)
cout<<"winnable"<<endl;
else
cout<<"hopeless"<<endl;
}
return 0;
}
第二种即是用SPFA,单源最短路来做。没怎么研究过图论,不怎么会SPFA,看了别人的代码写的,感觉是卡数据过的,当然有更优的算法,这里不再啰嗦了。
通过不断地进行松弛操作,使得存在回路的点对应的能量值不断变大变大,直到可以过五关斩六将,到达终点能量依然大于0为止。或者是能量不断变大的次数超过一个给定的值(这个值要很大很大,评测数据说了算),这时认为可能因为根本就到达不了终点,或加能量、减能量疲劳而死,返回假值。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<map>
#include<cctype>
#include<cmath>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define LL long long
#define INF 500000
using namespace std;
const int N=120;
bool G[N][N],vis[N];
int V[N],n,E[N];
int spfa()
{
queue<int> q;
memset(vis,0,sizeof(vis));
vis[0]=1;
q.push(0);
int cnt=0;
E[0]=100;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<n;i++)
{
if(G[u][i]&&E[i]<E[u]+V[i])
{
E[i]=E[u]+V[i];
if(!vis[i])
{
q.push(i);
vis[i]=1;
cnt++;
}
}
}
if(E[n-1]>0) return 1;
else if(cnt>INF) return 0;
vis[u]=0;
}
return 0;
}
int main()
{
while(cin>>n&&n!=-1)
{
memset(G,0,sizeof(G));
for(int i=0;i<n;i++)
{
cin>>V[i];
int k;
cin>>k;
for(int j=0;j<k;j++)
{
int b;
cin>>b;
G[i][b-1]=1;
}
}
memset(E,0,sizeof(E));
printf("%s\n",spfa()?"winnable":"hopeless");
}
return 0;
}
第三种即是用Bellman_Ford算法,也是一种求最短路的算法,spfa是他的优化版。但也有不同,后面讲一下。
这个题目实际上是求一个从起点到终点的最长路。
但是由于路过每个点时权值都不能为负,所以我们在初始化距离数组d[]时要初始化0。之后就是用队列优化的Bellman-Ford算法去求最长路了,但是还有一些细节需要注意。
我们当然可以在用这个算法时选择d[n]>0是就跳出循环来减少计算量,但也会找到使这个算法崩溃的例子,比如在某个位置存在一个正圈,而由这个正圈却不能到达终点,那么程序就会一直跑下去。但同样的,我们也不能见圈就跳,因为毕竟还是有些正圈可以到达终点的。这便要求我们去判断哪些正圈可以到达终点。
于是我们可以选择在找最长路之前先做一下预处理,把所有能够到达终点的点找出来,一个比较好的办法就是从终点开始逆向深搜,然后依次标记搜到的点即可,如果最后起点没有被标记,那就直接输出hopeless就可以了。
后面在使用队列优化的Bellman-Ford算法时,我们在判断条件上多加一个是否可达终点的判断即可,如果该点可达终点我们再进行求最长路的操作。
#include<stdio.h>
#include<string.h>
int G[110][110],w[110],d[110],n;
int q[110],inq[110],inedq[110];
int vis[110],reach[110];
void dfs(int v)
{
int u;
for(u=1;u<=n;u++)
if(G[u][v]&&!vis[u])
{
vis[u]=1;
reach[u]=1;
dfs(u);
}
}
int main()
{
int i,j,k,u,v,num,front,rear,flag;
while(1)
{
scanf("%d",&n);
if(n==-1)
break;
memset(G,0,sizeof(G));
for(u=1;u<=n;u++)
{
scanf("%d",&w[u]);
scanf("%d",&num);
for(i=0;i<num;i++)
{
scanf("%d",&v);
G[u][v]=1;
}
}
memset(vis,0,sizeof(vis));
memset(reach,0,sizeof(reach));
reach[n]=1;
dfs(n);
if(!reach[1])
{
printf("hopeless\n");
continue;
}
memset(inq,0,sizeof(inq));
memset(inedq,0,sizeof(inedq));
memset(d,0,sizeof(d));
front=rear=0;
d[1]=100;
q[rear++]=1;
inq[1]=1;
inedq[1]++;
flag=0;
while(front!=rear)
{
u=q[front++];
if(front>n)
front=0;
inq[u]=0;
for(v=1;v<=n;v++)
if(G[u][v]&&reach[v]&&d[u]+w[v]>d[v])
{
d[v]=d[u]+w[v];
if(!inq[v])
{
q[rear++]=v;
if(rear>n)
rear=0;
inq[v]=1;
if(inedq[v]++>n)
{
flag=1;
break;
}
}
}
if(d[n]>0||flag)
break;
}
if(d[n]>0||flag)
printf("winnable\n");
else
printf("hopeless\n");
}
return 0;
}
三种最短路算法比较:
Dijkstra
求单源、无负权的最短路。时效性较好,时间复杂度为O(V*V+E),可以用优先队列进行优化,优化后时间复杂
度变为0(v*lgn)。
源点可达的话,O(V*lgV+E*lgV)=>O(E*lgV)。
当是稀疏图的情况时,此时E=V*V/lgV,所以算法的时间复杂度可为O(V^2) 。可以用优先队列进行优化,优
化后时间复杂度变为0(v*lgn)。
Bellman-Ford
求单源最短路,可以判断有无负权回路(若有,则不存在最短路),时效性较好,时间复杂度O(VE)。
Bellman-Ford算法是求解单源最短路径问题的一种算法。
单源点的最短路径问题是指:给定一个加权有向图G和源点s,对于图G中的任意一点v,求从s到v的最短路径。
与Dijkstra算法不同的是,在Bellman-Ford算法中,边的权值可以为负数。设想从我们可以从图中找到一个环
路(即从v出发,经过若干个点之后又回到v)且这个环路中所有边的权值之和为负。那么通过这个环路,环路
中任意两点的最短路径就可以无穷小下去。如果不处理这个负环路,程序就会永远运行下去。 而Bellman-Ford
算法具有分辨这种负环路的能力。
SPFA
是Bellman-Ford的队列优化,时效性相对好,时间复杂度O(kE)。(k<<v)。
(k<<v)。
与Bellman-ford算法类似,SPFA算法采用一系列的松弛操作以得到从某一个节点出发到达图中其它所有节点的
最短路径。所不同的是,SPFA算法通过维护一个队列,使得一个节点的当前最短路径被更新之后没有必要立刻
去更新其他的节点,从而大大减少了重复的操作次数。
SPFA算法可以用于存在负数边权的图,这与dijkstra算法是不同的。
与Dijkstra算法与Bellman-ford算法都不同,SPFA的算法时间效率是不稳定的,即它对于不同的图所需要的时
间有很大的差别。
在最好情形下,每一个节点都只入队一次,则算法实际上变为广度优先遍历,其时间复杂度仅为O(E)。另一方
面,存在这样的例子,使得每一个节点都被入队(V-1)次,此时算法退化为Bellman-ford算法,其时间复杂度为
O(VE)。
SPFA算法在负边权图上可以完全取代Bellman-ford算法,另外在稀疏图中也表现良好。但是在非负边权图中,
为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法,以及它的使用堆优化的版本。通常的SPFA
PS:
而SPFA在这题中会超时,所以用BF算法搜索一下进行了优化,不过好像SPFA好像也可以这么做,代码复杂一些。