首先,HDU-2544 来回顾一下dij这个东西(链接: 这里)
讲解: (来源:坐在马桶上学算法) 不断对边进行松弛
(以下为copy)
算法的基本思想是:每次找到离源点(上面例子的源点就是 1 号顶点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。基本步骤如下:
- 将所有的顶点分为两部分:已知最短路程的顶点集合 P 和未知最短路径的顶点集合 Q。最开始,已知最短路径的顶点集合 P 中只有源点一个顶点。我们这里用一个 book[ i ]数组来记录哪些点在集合 P 中。例如对于某个顶点 i,如果 book[ i ]为 1 则表示这个顶点在集合 P 中,如果 book[ i ]为 0 则表示这个顶点在集合 Q 中。
- 设置源点 s 到自己的最短路径为 0 即 dis=0。若存在源点有能直接到达的顶点 i,则把 dis[ i ]设为 e[s][ i ]。同时把所有其它(源点不能直接到达的)顶点的最短路径为设为 ∞。
- 在集合 Q 的所有顶点中选择一个离源点 s 最近的顶点 u(即 dis[u]最小)加入到集合 P。并考察所有以点 u 为起点的边,对每一条边进行松弛操作。例如存在一条从 u 到 v 的边,那么可以通过将边 u->v 添加到尾部来拓展一条从 s 到 v 的路径,这条路径的长度是 dis[u]+e[u][v]。如果这个值比目前已知的 dis[v]的值要小,我们可以用新值来替代当前 dis[v]中的值。
- 重复第 3 步,如果集合 Q 为空,算法结束。最终 dis 数组中的值就是源点到所有顶点的最短路径。
(copy结束)如上图, 全部松弛完成之后的是这样的↑
差不多是从起点开始跑,到终点、跑完之后结束。
中间的过程,每次在可选择的点里面选一个没有跑过,并且距离最近的。
一旦被选定,这个点用过了,就要标记,把这个点的出度全部访问一遍,在这个过程中如果有未曾访问过的满足了可更新的条件,就去更新,把这条边的出度更新完为止。
【对于无向边,要加个2次,一样的!】呱呱呱,这就是算法的美妙之处吧1!!!
更新的是后面的点。每次选定的时候选剩下的最优的,从1-n即可。(所以说上面写的真好。。。)
然后过程中呢,
每次都存一下,然后最后跑完
【简单实现一下下】
代码: (18.4.7 ac one)
#include<vector>
#include<iostream>
#include<queue>
using namespace std;
struct node {
int next;
int dis;
bool operator< (const node &p)const {
return dis > p.dis;
}
node(int _next=0,int _dis=0):next(_next),dis(_dis){}
}h[105];
vector<node>e[105];//记录边的地方....
priority_queue<node> q;
bool vis[10005];//边
int dist[10005];
const int INF = 0x3f3f3f3f;
void dij(int x, int n) {
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) dist[i] = INF;
while (!q.empty()) q.pop();
dist[x] = 0;//...自己到自己是0 没错吧
q.push(node(x, 0));//吧自己也加进去..没错吧
while (!q.empty()){
node temp = q.top(); q.pop();
int u = temp.next;
if (vis[u]) continue;
vis[u] = true;
for (int i = 0; i < e[u].size(); i++){
int v = e[temp.next][i].next;
int cost = e[u][i].dis;
if (!vis[v] && dist[v] > dist[u] + cost){
dist[v] = dist[u] + cost;
q.push(node(v, dist[v]));
}
}
}
}
int main() {
int n, m; while (cin >> n >> m) {
if (m == 0 && n == 0)break;
int a1,a2,v;
for (int i = 0; i < n; i++)e[i].clear();
for (int i = 0; i < m; i++){
cin >> a1 >> a2 >> v;
e[a1].push_back(node(a2, v));//出度和值..
e[a2].push_back(node(a1, v));
}
dij(1, n);
long long ans = dist[n];
cout << ans << endl;
}
return 0;
}
【写后反思】dij这个东西,写的再熟练也不为过吧。啊…… 再熟练也不为过……
q如果没有满一样要清理,如果是循环输入的,把所有遍历到的东西都初始化一遍吧……
1.边可以存,但是怎么存?node里不是只有nxt和v吗?怎么保存当前的顶点嗯?
怎么初始化?不要忽略记录答案的dist[i]
q.push( x,0 ) ; 注意这里是push ,在队列里 而不是push_back
node temp = q.top() ;q.pop() ; 比如1可以到2和3,那么先遍历1、 1的v是0
【代码实现时间到】遍历1在g[1]里面的所有条件,其中已经确定住的不去遍历,没被确定的能更新就更新
(待续)
注意:vector<node> g
还有注意排序的时候的序号
2.要对整个过程熟悉,比如为什么要vis,为什么要inf,那是因为初始都是不可达,每次搞完一个就确定了,vis在比较的时候已经遍历了就不用管了
3.优先队列里不能写bool cmp ,那个是用在sort里面的
这么写:
bool operator < (node &p) {
return dis>p.dis;}
bool operator< (const node &p)const {
return dis > p.dis;
}
因为默认的优先队列里面,是优先级大的先出来
比如一个序列同样是12345
优先队列出来的顺序就会变成54321
因为是大的先出来
所以你要把里面的排序变成54321,它出来的才是12345 所以上面写的dis>p.dis
这的确是从小到大安排的。。 dis>p.dis
那么对于这个题... 有两种方法
for(int i = head[x]; i; i = nxt[i]){//开始松弛能到达的点
int u = to[i];//分两种情况
if(dis[x][lev] + val[i] < dis[u][lev]){//在不使任何新的边为0的情况下的松弛,相当于普通dijk
dis[u][lev] = dis[x][lev] + val[i];
if(!vis[u][lev]) q.push(Node(dis[u][lev],u,lev));//如果这个点这种状态还没当过出发点松弛就入队
}
if(lev + 1 <= k && dis[x][lev] < dis[u][lev+1]){//如果是这条边为0,再进行松弛,然后同上判断是否入队
dis[u][lev+1] = dis[x][lev];
if(!vis[u][lev+1]) q.push(Node(dis[u][lev+1],u,lev+1));
}
}
还有是分层读入
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&c);
u--;v--;
//分层
for (int j=0;j<=k;j++)
{
G[u*(k+1)+j].push_back(node(v*(k+1)+j,c));
if (j<k)
G[u*(k+1)+j].push_back(node(v*(k+1)+j+1,0));
}
}
都还没写