图论
序言:很高兴你能在电脑前点开这篇博客,一起点亮技能树。
- 图算法作为一种下至NOIP,上到IOI,ACM的重要算法,已经发展出了许多分支,这不仅导致各大算法竞赛中出现了更多图论的变种题,而且对图的理解和运用也越来越灵活。所以,掌握和灵活运用图论算法,是每个OIer都必须掌握的。
最短路算法
- 单源最短路:SPFA及优化 , dijkstra及优化
- 全源最短路:Floyd
注:不要看不起Floyd,Floyd算法本来就不是为单源最短路而生的。
SPFA及优化
关于SPFA,它死了,但是我们仍然要熟悉这个算法,因为在负权图上,SPFA的地位是无可替代的。
算法的基本流程:
从源点开始,将源点的dis=0,开始bfs广搜,维护一个先进先出的队列,对于连边进行松弛操作,将未在队列中的点加入队列,直至队列为空。通常复杂度 O(Km),K不太稳定,你懂得。
code:
#include<bits/stdc++.h>
using namespace std;
const int MAXM = 500000 + 5 , MAXN = 100000 + 5 ;
int N , M , S , tot = 0 , w[ MAXM ] , to[ MAXM*2 ] , nex[ MAXM*2 ] , head[ MAXM ] , dis[ MAXN ] ;
bool v[ MAXN ] ;
inline int read()
{
int s = 0,w = 1;
char g = getchar();
while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}
return s*w;
}
void add( int x , int y , int z ){
tot++ ;
to[ tot ] = y ;
w[ tot ] = z ;
nex[ tot ] = head[ x ] ;
head[ x ] = tot ;
}
void bfs( int x )
{
dis[ x ] = 0 ;
queue<int> q ;
q.push( x ) ;
while( !q.empty() ){
int u = q.front() ;
q.pop() ;
v[ u ] = false;
for( register int i = head[ u ] ; i ; i = nex[ i ] )
if( dis[ to[ i ] ] > dis[ u ] + w[ i ] ){
dis[ to[ i ] ] = dis[ u ] + w[ i ] ;
if( v[ to[ i ] ] == 0 ){
q.push( to[ i ] ) ;
v[ to[ i ] ] = true;
}
}
}
}
int main()
{
N = read() , M = read() , S = read() ;
for( register int i = 1 ; i <= N ; i++ )dis[ i ] = 210000000 ;
for( register int i = 1 ; i <= M ; i++ ){
int m1 = read() , m2 = read() , m3 = read() ;
add( m1 , m2 , m3 ) ;
// add( m2 , m1 , m3 ) ;
}
dis[ S ] = 0 ;
bfs( S ) ;
for( register int i = 1 ; i <= N ; i ++ )
if( dis[ i ] == 210000000)printf("2147483647 ");//不连通
else
printf("%d ", dis[ i ]) ;
return 0 ;
}
关于SPFA,它死了
关于SPFA它为什么死了?
不难发现稠密图会使运行次数明显上升(更多的松弛操作,点少边多),而且未优化的SPFA对于边的遍历顺序是可以通过边的读入顺序来控制,就是说,某些良心出题人可以通过特殊构造数据来让你对一个节点多次入队出队,然后卡到SPFA O(mn)的算法复杂度上界。
关于LLL优化,SLF优化,由于没有改变SPFA被卡的本质,所以,他们也死了。
所以在能写Dijkstra时别写SPFA。
这里ssw02想讲一下基于DFS实现的SPFA。
先声明,基于DFS的SPFA在最短路径问题中的表现极为...就算吸氧,也是过不了板子的,那么这个SPFA干什么呢?-->判负环
然后-->负环模板
博客会稍后跟上。
Dijkstra
本质是一种贪心(这和Prim有一丝类似)。
算法流程:不断用dis[]最小的节点进行更新操作。我们可以用priority_queue<>优化至 O(nlogn)的复杂度。 (这也是为什么Dijkstra无法在负权图上正常运作的原因)。
当然,如果数据允许,在一些不用求路径总长的情况下,我们可以给负边权同一加上一个值,使其转化为正值。(不实用的原因是这个附加值会被计算多次,不能求路径长)
直接放板子:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100010 , MAXM = 200010 ;
int head[ MAXN ] , to[ MAXM*2 ] , w[ MAXM*2 ] , nex[ MAXM*2 ] , tot = 1 ;
int N , M , S , dis[ MAXN ] ;
bool v[ MAXN ] ;
inline int read(){
int s = 0,w = 1;
char g = getchar();
while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
while(g>='0'&&g<='9'){s = (s<<1)+(s<<3)+g-'0';g = getchar();}
return s*w;
}
priority_queue< pair< int , int > >q ;//pair第一维维护dis相反数,第二维维护节点
void add( int x , int y , int z ){
to[ ++tot ] = y , w[ tot ] = z , nex[ tot ] = head[ x ] , head[ x ] = tot ;
}
void dijkstra( int u ){
for( register int i = 1 ; i <= N ; i++ )dis[ i ] = (1<<30) , v[ i ] = false ;
dis[ u ] = 0 ;
q.push( make_pair(0,u) ) ;
while( !q.empty() ){
int u = q.top().second ; q.pop() ;
if( v[ u ] )continue ;
v[ u ] = true ;
for( register int i = head[ u ] ; i ; i = nex[ i ] ){
if( dis[ to[ i ] ] > dis[ u ] + w[ i ] ){
dis[ to[ i ] ] = dis[ u ] + w[ i ] ;
q.push( make_pair( -dis[ to[ i ] ] , to[ i ] ) ) ;
}
}
}
}
int main(){
N = read() , M = read() , S = read() ;
for( register int i = 1 ; i <= M ; i++ ){
int m1 = read() , m2 = read() , m3 = read() ;
add( m1 ,m2 ,m3 ) ;
}
dijkstra( S ) ;
for( register int i = 1 ; i <= N ; i++ )printf("%d ",dis[ i ] ) ;
return 0 ;
}
最短路基础到此。
下一章ssw02将总结最短路的应用(还有一些毒瘤题)
传送门:(待更)