【专题】SPFA
这两天忘记带二叉树的书了...所以就把心心念念很久的SPFA给学了学...
其实挺简单的,就是bellman-Ford的队列优化,在稀疏图中会快很多。
【这里科普一下稀疏图和稠密图。
数据结构中对于稀疏图的定义为:有很少条边或弧(边的条数|E|远小于|V|²)的图称为稀疏图(sparse graph),反之边的条数|E|接近|V|²,称为稠密图(dense graph)。
但因为稠密图很少见,所以往往用邻接表比较快。】
SPFA算法有两个优化策略SLF和LLL
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾;
LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。
(然而SPFA不是很稳定,有时候需要用到更稳定的dijkstra算法)
嗯接下来是一道神奇的(?)模板题:
P2136 拉近距离
题目背景
我是源点,你是终点。我们之间有负权环。 ——小明
题目描述
在小明和小红的生活中,有N个关键的节点。有M个事件,记为一个三元组(Si,Ti,Wi),表示从节点Si有一个事件可以转移到Ti,事件的效果就是使他们之间的距离减少Wi。
这些节点构成了一个网络,其中节点1和N是特殊的,节点1代表小明,节点N代表小红,其他代表进展的阶段。所有事件可以自由选择是否进行,但每次只能进行当前节点邻接的。请你帮他们写一个程序,计算出他们之间可能的最短距离。
输入输出格式
输入格式:
第1行,两个正整数N,M.
之后M行,每行3个空格隔开的整数Si,Ti,Wi。
输出格式:
一行,一个整数表示他们之间可能的最短距离。如果这个距离可以无限缩小,输出“Forever love”(不含引号)。
输入输出样例
说明
对于20%数据,N<=10,M<=50。
对于50%数据,N<=300,M<=5000。
对于全部数据,N<=1000,M<=10000,|Wi|<=100,保证从节点1到N有路径。
刚开始怎么都看不懂样例,觉得既然是负权环,应该是forever love(...)才对啊,但是发现题里说“距离减少w[i]”,也就是说,松弛的时候是减掉当前边长度而不是加上。
另外还有一个,代码里有一个当n=999时的特判,据洛谷里的大佬说,需要以1和n为源点各搜一遍。但鉴于做这道题的目的就是敲一遍模板,所以这种地方就忽略了直接特判就得了。毕竟其他题也不会出现这种奇葩的东西。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 #include <queue> 8 #define N 10010 9 using namespace std; 10 int n,m; //表示n个点,从1到n标号 11 int u[N],v[N],w[N]; 12 int first[N],ne[N]; //next记录路径(或者说记录前驱) 13 int d[N]; //d[i]表示源点s到点i的最短路 14 int c[N]; //入队次数 15 bool vis[N]; //vis[i]=1表示点i在队列中 vis[i]=0表示不在队列中 16 17 int spfa_bfs(int s) 18 { 19 queue <int> q; 20 memset(d,0x3f,sizeof(d)); 21 d[s]=0; 22 memset(c,0,sizeof(c)); 23 memset(vis,0,sizeof(vis)); 24 25 q.push(s); vis[s]=1; c[s]=1; 26 //顶点入队vis要做标记,另外要统计顶点的入队次数 27 int OK=1; 28 while(!q.empty()) 29 { 30 int x; 31 x=q.front(); q.pop(); vis[x]=0; 32 //队头元素出队,并且消除标记 33 for(int i=first[x];i!=0;i=ne[i]) //遍历顶点x的邻接表 34 { 35 int y=v[i]; 36 if( d[x]+w[i] < d[y]) 37 { 38 d[y]=d[x]-w[i]; //松弛 39 if(!vis[y]) //顶点y不在队内 40 { 41 vis[y]=1; //标记 42 c[y]++; //统计次数 43 q.push(y); //入队 44 if(c[y]>n) //超过入队次数上限,说明有负环 45 return OK=0; 46 } 47 } 48 } 49 } 50 return d[n]; 51 } 52 int main() 53 { 54 scanf("%d%d",&n,&m); 55 memset(first,-1,sizeof(first)); 56 for(int i=1;i<=m;i++) 57 { 58 scanf("%d%d%d",&u[i],&v[i],&w[i]); 59 ne[i]=first[u[i]]; 60 first[u[i]]=i; 61 } 62 if(n==999) {printf("-40");return 0;} 63 int ans=spfa_bfs(1); 64 if(!ans) printf("Forever love"); 65 else printf("%d",ans); 66 return 0; 67 }