dijkstra算法是一种最短路径算法
用于计算单源最短路径,即从一个源点出发,到图中其他所有点的最短路径
要求是所有边的权值都为正
过程
令源点为s
建立两个数组dis,vis。dis[i]表示从源点出发到编号为i的点的距离 (初始时dis[s]=0,其他所有点的dis值为无穷大,在计算过程中,如果找到一条到达点 i 的更短的路径,dis[i]将更新为这条更短路径的距离) vis[i]表示 i 点的dis[i] 是否为真正的最短路径(vis[i]==true 表示 i 点的dis值已经确定是最小值,不能更短了)初始时所有点的vis值都为false。
每一轮中挑选出所有 vis 值为false的点中dis值最小的那一个,令为点 u ,这时可以保证 u 点的dis值为最短的路径,将其vis值更改为true。
更新点 u 周围的点的dis值,规则是:如果存在一条从 u 到 v 的权值为 w 的边,则我们可以找到一条路径从源点到u点,再经过这条边到达 v 点的路径,且其长度为dis[u]+w,如果比现在的dis[v]小的话,那么就找了一条更短的路径,更新dis[v]的值
重复过程2、3,直至所有点的vis值都为true。
证明
简单的口述证明一下 (懂的同学也看看,有些名词后面要用)
首先我们可以将图中的点分为三类,
实际上,上述过程2中每次挑选的点 u 都是边界点,因为外部点的dis值为无穷大
到达点 u 的真正最短路径首先必经过一些内部点(点 u 为源点除外) ,如果经过了边界点 k,那么这条路的长度必然大于dis[k](因为所有边的权值都大于零,路径只可能越走越长),而dis[u]是所有边界点中最短的,即dis[u]<=dis[k] 所以到达 u 的最短路径不可能经过其他边界点。
其实上述证明不够严谨,因为dis[k]不一定是最短路径,可能之后dis[k] 可能更新为更小的值,但是更新后的值也一定大于dis[u] 因为每次跟更新都是由边界点向外更新(之后才把边界点纳入内部点)而dis[u]已经是边界点中最小的了,之后更新的值也肯定比dis[u]更大。
堆优化
我们已经知道原理,接下来要用算法实现,其中最重要的就是,怎么才能在步骤2中快速挑选出边界点中dis值最小的那个,堆正适合这项工作(不懂的同学先去别的地方看看堆是什么)既能在O(1)时间内找出最小值,又能在O(logn)时间将被更新的点的dis值加入到堆中排序。
代码
首先贴一下我的宏
#define fors(x,n) for(int x = 0,end = n; x < end; x++)
再贴一下我存图的代码
#define MAX_NODE 1000000+20
#define MAX_EDGE 10000000+20
int edge_cnt = 0;
int head[MAX_NODE];
Edge edges[MAX_EDGE];
void init() {
memset(head, 0, sizeof head);
edge_cnt = 0;
}
void add_edge(int from, int to, int weight) {
edges[++edge_cnt] = Edge(weight, to, head[from]);
head[from] = edge_cnt;
}
向前星,不懂的可以搜搜,一种类链式存储结构
接下来是dijkstra的代码
bool vis[MAX_NODE];
int dis[MAX_NODE];
int dijkstra(int s, int t) {
fors(i, MAX_NODE) {
vis[i] = false;
dis[i] = INT_MAX;
}
dis[s] = 0;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
heap.push(make_pair(0, s));
while (true) {
int u = heap.top().second;
heap.pop();
if (u == t) return dis[t];
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (dis[v] > dis[u] + edges[i].weight) {
dis[v] = dis[u] + edges[i].weight;
heap.push(make_pair(dis[v], v));
}
}
}
}
简单说明一下
s为源点,t为终点,u、v含义同上述
堆使用了c++自带的priority_queue,因为标准库默认是最大堆(太坑了T^T),所以要自己设置比较结构体
这里直接用了greater
首先将源点设为边界点dis设为0,其余点为外部点。
将所有边界点加入堆中,每次取堆顶元素,注意,堆顶元素可能已经不是边界点而是内部点了,这是因为每个点的dis值可能会更新多次,每次都会入堆,最小的那个dis值最先出堆,出堆时更新其周围点的dis值,并将其加入到内部点中,之后出堆的就不用管了,直接continue.
这里只要找到终点的最短路径就提前退出了,如果想求所有点的最短路径的话,可以改成这样
bool vis[MAX_NODE];
int dis[MAX_NODE];
void dijkstra(int s) {
fors(i, MAX_NODE) {
vis[i] = false;
dis[i] = INT_MAX;
}
dis[s] = 0;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
heap.push(make_pair(0, s));
while (!heap.empty()) {
int u = heap.top().second;
heap.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (dis[v] > dis[u] + edges[i].weight) {
dis[v] = dis[u] + edges[i].weight;
heap.push(make_pair(dis[v], v));
}
}
}
}
算法结束后取dis[i]即为s到i最短路径
还没完!!!!
上述算法中,如果一个点的dis值被更新多次,那么它的dis值会被多次入堆。。
多次就多次呗,咱也不差那点内存。。
但是!!!!
就是又这种丧心病狂的题卡你内存。。。
比如这题。。。HYSBZ - 3040 链接
可以避免重复入堆吗?可以的,但是标准库不支持,所以要自己写
这里我写了一个配对堆,即如果一个点在堆中,直接在堆里更改它的dis值,并重排堆元素,时间也是O(logn)的,贴上代码:
template<typename V, typename C = std::less<V> >
class my_heap {
typedef int Key;
typedef pair<Key, V> node;
deque<node> heap;
int* poses;
C cmp;
void move(int from, int to) {
heap[to] = heap[from];
poses[heap[from].first] = to;
}
void update_down(int pos) {
node temp = heap[pos];
for (int i = pos * 2 + 1, end = heap.size(); i < end; pos = i, i = i * 2 + 1) {
if (i + 1 < end && cmp(heap[i].second, heap[i + 1].second))i++;
if (cmp(temp.second, heap[i].second)) move(i, pos);
else break;
}
poses[temp.first] = pos;
heap[pos] = temp;
}
void update_up(int pos) {
node temp = heap[pos];
for (int i = (pos + 1) / 2 - 1; i >= 0; pos = i, i = (i + 1) / 2 - 1) {
if (cmp(heap[i].second, temp.second)) move(i, pos);
else break;
}
poses[temp.first] = pos;
heap[pos] = temp;
}
public:
my_heap(int key_cnt) {
poses = new int[key_cnt + 1];
for (int i = 0; i <= key_cnt; i++)
poses[i] = -1;
}
~my_heap() { delete[] poses; }
bool has(Key key) { return poses[key] != -1; }
void push(Key key, const V & val) {
heap.push_back(make_pair(key, val));
poses[key] = heap.size() - 1;
update_up(heap.size() - 1);
}
const node& top() { return heap[0]; }
bool empty() { return heap.empty(); }
void pop() {
int temp_key = heap[0].first;
move(heap.size() - 1, 0);
poses[temp_key] = -1;
heap.pop_back();
if (!empty()) update_down(0);
}
void update(Key key, const V & new_val) {
int pos = poses[key];
heap[pos].second = new_val;
update_up(pos);
update_down(pos);
}
const V& get_val(Key key) { return heap[poses[key]].second; }
};
简单说明一下功能:
这里入堆使用push函数,第一个参数为key,第二个参数为value,堆中会对value值进行排序,默认最大堆,如果要用最小堆请传入大于比较结构体(跟标准库统一。。)
构造函数的参数为key的数量key_cnt,之后传入的key的值只能是 [0,key_cnt] 区间的整数(散列表或者key不是int型的话在可以在堆外部建立一个hash表来转化为key)
这里key即为点的标号
使用has函数查询key是否在堆中
如果保证一个key在堆中那么可以直接使用update函数在堆中更改相应的value。
get_val函数用来获得堆中key的value,前提也是key在堆中
于是dijkstra可以这样写
int pre[MAX_NODE];
int dijkstra(int s, int t) {
fors(i, MAX_NODE) pre[i] = -1;
pre[s] = s;
my_heap<int, greater<int> > heap(MAX_NODE);
heap.push(s, 0);
while (true) {
pair<int, int> top = heap.top();
heap.pop();
if (top.first == t) return top.second;
for (int i = head[top.first]; i; i = edges[i].next) {
int to = edges[i].to;
if (heap.has(to) && heap.get_val(to) > top.second + edges[i].weight) {
heap.update(to, top.second + edges[i].weight);
pre[to] = top.first;
}
else if (pre[to] == -1) {
heap.push(to, top.second + edges[i].weight);
pre[to] = top.first;
}
}
}
}
dis数组和vis数组不见了!
取而代之的是一个pre数组,pre[i]=k 表示目前的所有到点 i 的路径中,最短的一条的最后一步是从 k 到 i 的一条边
那么如何分辨三类点呢??
每个点的dis值都暂存于堆中,出堆时会丢失,也就是说出堆时是确定某个点的最短路径的唯一时机,这时如果时终点话,直接返回
当然也可以使用dis+vis的方法,这么做的优点是:可以确定最短路径具体是经过那些店到达终点的
只要从终点出发,沿着pre数组一直向前找,直至找到源点就行了。
下面是整题的ac代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace::std;
#define ll long long
#define fors(x,n) for(int x = 0,end = n; x < end; x++)
template<typename V, typename C = std::less<V> >
class my_heap {
typedef int Key;
typedef pair<Key, V> node;
deque<node> heap;
int* poses;
C cmp;
void move(int from, int to) {
heap[to] = heap[from];
poses[heap[from].first] = to;
}
void update_down(int pos) {
node temp = heap[pos];
for (int i = pos * 2 + 1, end = heap.size(); i < end; pos = i, i = i * 2 + 1) {
if (i + 1 < end && cmp(heap[i].second, heap[i + 1].second))i++;
if (cmp(temp.second, heap[i].second)) move(i, pos);
else break;
}
poses[temp.first] = pos;
heap[pos] = temp;
}
void update_up(int pos) {
node temp = heap[pos];
for (int i = (pos + 1) / 2 - 1; i >= 0; pos = i, i = (i + 1) / 2 - 1) {
if (cmp(heap[i].second, temp.second)) move(i, pos);
else break;
}
poses[temp.first] = pos;
heap[pos] = temp;
}
public:
my_heap(int key_cnt) {
poses = new int[key_cnt + 1];
for (int i = 0; i <= key_cnt; i++)
poses[i] = -1;
}
~my_heap() { delete[] poses; }
bool has(Key key) { return poses[key] != -1; }
void push(Key key, const V & val) {
heap.push_back(make_pair(key, val));
poses[key] = heap.size() - 1;
update_up(heap.size() - 1);
}
const node& top() { return heap[0]; }
bool empty() { return heap.empty(); }
void pop() {
int temp_key = heap[0].first;
move(heap.size() - 1, 0);
poses[temp_key] = -1;
heap.pop_back();
if (!empty()) update_down(0);
}
void update(Key key, const V & new_val) {
int pos = poses[key];
heap[pos].second = new_val;
update_up(pos);
update_down(pos);
}
const V& get_val(Key key) { return heap[poses[key]].second; }
};
struct Edge {
int weight;
int to;
int next;
Edge(int weight, int to, int next) :weight(weight), to(to), next(next) {}
Edge() = default;
};
#define MAX_NODE 1000000+20
#define MAX_EDGE 10000000+20
int edge_cnt = 0;
int head[MAX_NODE];
Edge edges[MAX_EDGE];
void init() {
memset(head, 0, sizeof head);
edge_cnt = 0;
}
void add_edge(int from, int to, int weight) {
edges[++edge_cnt] = Edge(weight, to, head[from]);
head[from] = edge_cnt;
}
int pre[MAX_NODE];
int dijkstra(int s, int t) {
fors(i, MAX_NODE) pre[i] = -1;
pre[s] = s;
my_heap<int, greater<int> > heap(MAX_NODE);
heap.push(s, 0);
while (true) {
pair<int, int> top = heap.top();
heap.pop();
if (top.first == t) return top.second;
for (int i = head[top.first]; i; i = edges[i].next) {
int to = edges[i].to;
if (heap.has(to) && heap.get_val(to) > top.second + edges[i].weight) {
heap.update(to, top.second + edges[i].weight);
pre[to] = top.first;
}
else if (pre[to] == -1) {
heap.push(to, top.second + edges[i].weight);
pre[to] = top.first;
}
}
}
}
int main() {
ll n, m, t, rxa, rxc, rya, ryc, rp, x, y, z, a, b;
cin >> n >> m >> t >> rxa >> rxc >> rya >> ryc >> rp;
x = y = z = 0;
init();
fors(i, t) {
x = (x * rxa + rxc) % rp;
y = (y * rya + ryc) % rp;
a = min(x % n, y % n);
b = max(x % n, y % n);
add_edge(a, b, 1e8 - 100 * (a+1));
}
fors(i, m - t) {
scanf("%lld%lld%lld", &x, &y, &z);
add_edge(x - 1, y - 1, z);
}
cout << dijkstra(0, n - 1) << endl;
}