Bellman-Ford算法,dijstra算法,循环dp问题
这题很好,让我重新认识到了最短路到底是为解决了一个什么样的问题!!!!
题意:
分析:
首先,我不会分层图!!!!
所以,这题我主要是围绕dp展开。
我们必须要知道题中所给函数1/(1-x)是个周期为三的周期函数
那么边权就为x,1/(x-1),(x-1)/x 这里是取过绝对值的
这是前提,没有意识到这一点无从展开的。
这里我给一个dp状态
dp[i][j]:点i在j状态时到达 状态为0的节点1 的最短路径
j状态共有三种对应上面周期函数的每一种变化[x,1/(x-1),(x-1)/x]
那我们所求的答案就是min(dp[n][0],dp[n][1],dp[n][2])
理解?
状态转移方程为:
dp[i][j] = min(d[k][(j-1)%3] + e[i][k].cost[(j-1)%3],k为j的相邻点 (j+1)%3为前一个状态)
是否?这应该就是我们的状态转移方程。且我们知道初始状态d[1][0] = 0
但是啊,我们发现如果我们按照这个方程去转移递归的话,我们会陷入死循环!!
仔细想想,我们会在两个相邻节点之间来回递归永远出不去。准确说,我们会在环间来回递归,永远出不去!
这是很惨的!循环dp!!!!
正当我一筹莫展的时候,我翻开了Bellman-Ford算法,恍然大悟!!!
请看:
我们发现,Bellman-Ford实际上也是总结出了一个循环dp :
d[i] = min(d[j]+(从j到i的cost)|e=(j,i)属于E)
在下面的说明中也说了,如果图中有环则无法计算,会像我们一样无限递归。
这是不是和我们的dp所面临的问题一样?
而他是如何解决这个问题的呢?
松弛操作!!!!!
Bellman-Ford通过松弛操作解决了dp循环问题。而松弛操作的前提是没有负循环,我们当然没有负循环,我们连负边都没有!!
所以我们可以实行Bellman的松弛操作!
众所周知,Dijstra算法和SPFA算法不过是Bellman-Ford算法的技术优化,其原理和操作未变。
SPFA算法没有额外条件还是只要没有负环就可以了。Dijstra算法则是还需要没有负边,我们没有负边
所以我们这里选择最优的Dijstra算对Bellman-Ford算法进行优化。
如果写不出的话,建议仔细看看Bellman-Ford算法怎么写的,用Bellman-Ford算法写了本题后,再用Dijstra算法优化。
代码如下:
#include<iostream> #include<algorithm> #include<vector> #include<queue> #include<functional> #include<iomanip> using namespace std; typedef pair<int, int> pii; typedef pair<double, pii> pdi; struct edge { int to;vector<double> cost; }; const int max_n = 1e5 + 100; const double inf = 1e18; vector<edge> G[max_n]; int n, m; double d[max_n][3]; void dijstra(int s,int t) { for (int i = 1;i <= n;i++) for (int j = 0;j < 3;j++) d[i][j] = inf; d[s][t] = 0; priority_queue<pdi, vector<pdi>, greater<pdi>> que; que.push({ d[s][t],{s,t} }); while (!que.empty()) { pdi p = que.top();que.pop(); pii pos = p.second; if (d[pos.first][pos.second] < p.first)continue; for (int i = 0;i < G[pos.first].size();i++) { edge e = G[pos.first][i]; if (d[e.to][(pos.second + 1) % 3] > d[pos.first][pos.second] + e.cost[pos.second]) { d[e.to][(pos.second + 1) % 3] = d[pos.first][pos.second] + e.cost[pos.second]; que.push({ d[e.to][(pos.second + 1) % 3],{e.to,(pos.second + 1) % 3} }); } } } } int main() { ios::sync_with_stdio(0); cin >> n >> m; for (int i = 1;i <= m;i++) { int u, v;double w;cin >> u >> v >> w; edge e1;e1.to = u;e1.cost = { w,1 / (w - 1),(w - 1) / w }; G[v].push_back(e1); edge e2;e2.to = v;e2.cost = { w,1 / (w - 1),(w - 1) / w }; G[u].push_back(e2); } dijstra(1, 0); double ans = inf; for (int i = 0;i < 3;i++)ans = min(ans, d[n][i]); if (ans == inf)cout << -1 << endl; else cout << fixed << setprecision(3) << ans << endl; }
关键在于对松弛操作的理解,他到底解决了什么问题? 无负循环的循环dp问题!!!!!!