【存储方式】
(1)邻接矩阵
空间复杂度O(N^2),简单来说就是一个二维数组G[N][N],G[i][j]为INF表示i->j没有边,否则表示边的长度.
遍历查找与一个点相连的所有边的时间复杂度为O(N).太慢了,而且空间消耗太大。
基本用来卖萌。
(2)链式前向星
基本上和邻接表没什么区别,而且也很容易理解。(这里不阐述原理,只给出代码)。
int N,M; //分别表示点数、边数
int head[N]; //第i个点的第一条边的标号
int next[M]; //与某个点相连的某条边的下一条边标号
int end[M]; //某条边所指向的点的标号
int len[M]; //某条边的长度
int ind=2; //当前边的标号,初始为2有利于网络流等算法
void addedge(int a,int b,int _len)
{
int q = ind++;
end[q] = b;
len[q] = _len;
next[q] = head[a];
head[a] = q;
}
//遍历点x
for(register int tmp = head[x] ; tmp ; tmp = next[tmp])
{
......
}
自行脑补吧。。。
【最短路算法】
Dijkstra算法(加入堆优化) O(nlogn) (单源最短路、不适用于负权边)
思想就是每次从不在最短路的点集中选出距源点距离最短的点,然后把这个点加入最短路点集,然后用这个点维护与它相连的(指向的)点的距源点距离。这样进行n次使得图中所有的点都在最短路点集中。开始时源点的dis值为0,其余点的dis值为INF.
选出距源点距离最短的点这一步本来需要O(n)的遍历,不过使用堆优化可以降至O(logn).总时间复杂度为O(nlogn).
更邪恶一点的C++党用优先级队列直接水。(具体怎么用往下看)
*水一发*->NOI2010【海拔】
不知题意的百度一下。
本弱渣打眼一看立马蒙圈,而某些大神狗眼一看就知道海拔只有0,1。。。狗眼再一看就知道海拔为连续0,1块。(不明觉厉)然后直接最小割->最大流,以左上角为源点,右下角为汇点求一下最大流即可。不过这样只有80-90。
为什么80-90呢,因为最后的点的数目比较多。会TLE一些点。
观察可得这图构造得比较和谐,然后可以构造它的对偶图(见08年国家队论文day2)转化为最短路,细节到那里去脑补。
说一下构图的方法。
由于对偶图是以平面图的面来做点的,所以对偶图有N^2+2个点,多出来那两个是源点和汇点。
边的方向?
本蒟蒻画了一幅图。。。
以左下角为起点,右上角为终点,中间的格子为点,建图。
粉色是实际边的方向(总是由0块指向1块),红色是连边的方向(由起点指向终点)。
具体来说,下边向右连,左边向下连,右边向上连,上边向左连。
然后直接水一发最短路。
由于是稠密图,spfa不幸超时。。。只好用堆优化dijkstra...
代码如下:
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
struct Node
{
int ord , dist;
Node(int _ord = 0, int _dist = 0):ord(_ord),dist(_dist){}
bool operator < (const Node &b) const
{
return dist > b.dist;
}
};
priority_queue<Node> q;
int head[250010] , next[1100000] , end[1100000] , len[1100000] , ind;
void addedge(int a , int b , int _len)
{
int q = ++ind;
end[q] = b;
len[q] = _len;
next[q] = head[a];
head[a] = q;
}
int S , T , n;
int mindis[250010];
void dijkstra()
{
memset(mindis , 0x3f , sizeof(mindis));
mindis[S] = 0;
q.push(Node(S , mindis[S]));
while(!q.empty())
{
Node X = q.top();
q.pop();
int ins = X.ord;
if(X.dist > mindis[ins]) continue;
for(register int tmp = head[ins]; tmp ; tmp = next[tmp])
{
if(mindis[end[tmp]] > mindis[ins] + len[tmp])
{
mindis[end[tmp]] = mindis[ins] + len[tmp];
q.push(Node(end[tmp] , mindis[end[tmp]]));
}
}
}
}
int main()
{
register int i,j;
scanf("%d",&n);
S = 0 , T = n * n + 1;
int x;
//west->east
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge(i , T , x);
}
for(i = 1 ; i < n ; ++i)
{
for(j = 1 ; j <= n ; ++j)
{
scanf("%d",&x);
addedge(i * n + j , (i - 1) * n + j , x);
}
}
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge(S , n * (n - 1) + i , x);
}
//north->south
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge(S , (i - 1) * n + 1 , x);
for(j = 1 ; j < n ; ++j)
{
scanf("%d",&x);
addedge((i - 1) * n + j , (i - 1) * n + j + 1 , x);
}
scanf("%d",&x);
addedge(i * n , T , x);
}
//east->west
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge(T , i , x);
}
for(i = 1 ; i < n ; ++i)
{
for(j = 1 ; j <= n ; ++j)
{
scanf("%d",&x);
addedge((i - 1) * n + j , i * n + j , x);
}
}
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge(n * (n - 1) + i , S , x);
}
//south->north
for(i = 1 ; i <= n ; ++i)
{
scanf("%d",&x);
addedge((i - 1) * n + 1 , S , x);
for(j = 1 ; j < n ; ++j)
{
scanf("%d",&x);
addedge((i - 1) * n + j + 1 , (i - 1) * n + j , x);
}
scanf("%d",&x);
addedge(T , i * n , x);
}
dijkstra();
printf("%d",mindis[T]);
return 0;
}
spfa算法 时间复杂度O(kE) 对于稀疏图可证k<=2,对于稠密图。。。
很吊的最短路算法,效率比较高而且很好写,能够灵活地维护一些信息。(学名叫做“松弛技术”)
不过对于稠密图效率大大降低。
先来看一看朴素的单源最短路径长度。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int Maxn = 1001;
int dis[Maxn];
bool inqueue[Maxn];
void sqfa(int S) //起点为S
{
memset(dis , 0x3f , sizeof(dis)); //保证2倍不会爆int的大数
dis[S] = 0;
memset(inqueue , 0 , sizeof(inqueue));
inqueue[S] = 1;
queue<int> q;
q.push(S);
while(!q.empty())
{
int ins = q.front();
q.pop();
inqueue[ins] = 0; //不要忘了!!
for(int tmp = head[ins] ; tmp ; tmp = next[tmp]) //链式前向星
{
int to = end[tmp];
if(dis[to] > dis[ins] + len[tmp])
{
dis[to] = dis[ins] + len[tmp];
if(!inqueue[to])
{
inqueue[to] = 1;
q.push(to);
}
}
}
}
}
调用spfa()后,dis[]数组存放每一个点到起点的距离。
(建议你们试一下poj1062 体会一下)
spfa是不怕负权边的,不过对于一些没有最短路的图来说,就会陷入死循环。这些图有负权回路。判定负环的方法很简单,依旧利用spfa求解最短路。假设图中有N个点,记录每个点入队被松弛的次数,如果一个点被入队松弛了超过N次,则图中一定存在负权回路。(显然可证)