最短路训练题单和题解(从易到难)(更新中)

文章详细介绍了多种基于最短路径的图论算法,包括Dijkstra算法在单源最短路径问题中的应用,USACO竞赛中的AppleDeliveryS问题解决方案,最小花费问题的贪心策略,以及涉及最短路径计数和负权边处理的复杂情况。这些例子展示了如何在实际问题中运用图论算法来求解最优化问题。
摘要由CSDN通过智能技术生成

题单:最短路

目录

题单:最短路

P4779【模板】单源最短路径(标准版) 

题意:

题解:

代码:

P3003 [USACO10DEC]Apple Delivery S

题意:

题解:

代码:

P1576 最小花费

题意:

题解:

代码:

P1690 贪婪的Copy

题意:

题解:

代码:

P1073 [NOIP2009 提高组] 最优贸易

题意:

题解:

代码:

P1608 路径统计

题意:

题解:

代码:

P1613 跑路

P5905 【模板】Johnson 全源最短路

P1099 [NOIP2007 提高组] 树网的核

P2169 正则表达式

P2407 [SDOI2009]地图复原

P2371 [国家集训队]墨墨的等式


P4779【模板】单源最短路径(标准版) 

题意:

给定一个 n 个点,m 条有向边的带非负权图,请你计算从 s出发,到每个点的距离。数据保证你能从 s 出发到任意点。(模板)

题解:

有向边,没有负边权,可以用Dijkstra

代码:

#include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 const int inf = (1<<31)-1;
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
  vector<int> dijkstra(int n,int s,vector<vector<pair<int,int>>>&to){
     vector<int>dist(n+1, inf);
     vector<int>st(n+1,0);
     // 定义小根堆,用于存储未确定最短路的节点
     priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
     // 将源节点s加入小根堆
     pq.push(make_pair(0, s));
     dist[s]=0;
     while (!pq.empty()) {
         // 取出堆顶节点u
         int u = pq.top().second;
         pq.pop();
         if(st[u])continue;
         st[u]=1;
         // 遍历u的邻接节点v
         for (auto& e : to[u]) {
             int v = e.first;
             int w = e.second;
             // 如果u到v的距离更短,则更新dist[v]和prev[v]的值,并将v加入小根堆
             if (dist[u] + w < dist[v]) {
                 dist[v] = dist[u] + w;
                 pq.push(make_pair(dist[v], v));
             }
         }
     }
     return dist;
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n,m,s;
     cin>>n>>m>>s;
     vector<vector<pair<int,int>>>to(n+1);
     for (int i = 0; i < m; i++)
     {
         int x,y,w;
         cin>>x>>y>>w;
         to[x].push_back({y,w});
     }
     vector<int>ans=dijkstra(n,s,to);
     for (int i = 1; i <= n; i++)
     {
         cout<<ans[i]<<" \n"[i==n];
     }
     return 0;
 }

P3003 [USACO10DEC]Apple Delivery S

题意:

无向图,无负环,从起点走过两点的最短距离(从起点开始,连通3个点的最短路径)

题解:

不管怎么走,都要有两条路连通这3个点,连通2个点的最优情况就是他们的最短路,所以我们要选择两条最短路连通这三个点,从起点开始,另外两点必须连接,情况少,我们直接判断。

代码:

 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
  vector<int> dijkstra(int n,int s,vector<vector<pair<int,int>>>&to){
     vector<int>dist(n+1, inf);
     vector<int>st(n+1,0);
     // 定义小根堆,用于存储未确定最短路的节点
     priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
     // 将源节点s加入小根堆
     pq.push(make_pair(0, s));
     dist[s]=0;
     while (!pq.empty()) {
         // 取出堆顶节点u
         int u = pq.top().second;
         pq.pop();
         if(st[u])continue;
         st[u]=1;
         // 遍历u的邻接节点v
         for (auto& e : to[u]) {
             int v = e.first;
             int w = e.second;
             // 如果u到v的距离更短,则更新dist[v]和prev[v]的值,并将v加入小根堆
             if (dist[u] + w < dist[v]) {
                 dist[v] = dist[u] + w;
                 pq.push(make_pair(dist[v], v));
                 
             }
         }
     }
     return dist;
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n,m,s,a,b;
     cin>>m>>n>>s>>a>>b;
     vector<vector<pair<int,int>>>to(n+1);
     for (int i = 0; i < m; i++)
     {
         int x,y,w;
         cin>>x>>y>>w;
         to[x].push_back({y,w});
         to[y].push_back({x,w});
     }
     vector<int>ans1=dijkstra(n,s,to);
     vector<int>ans2=dijkstra(n,a,to);
     cout<<min(ans1[a],ans1[b])+ans2[b];
     return 0;
 }

P1576 最小花费

题意:

在 n 个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A 最少需要多少钱使得转账后 B 收到 100 元。

题解:

边权是扣除z%的手续费,就是乘(100-z)/100,是求路径乘积最大值,边权都是小于1的,越少边越好

满足最优子结构(如果w_{ac}=w_{ab}*w_{bc}是a->c的最优路径,则w_{bc}一定是b->c的最优路径),直接用dijkstra做

代码:

 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
  vector<double> dijkstra(int n,int s,vector<vector<pair<int,int>>>&to){
     vector<double>dist(n+1, 0);
     vector<int>st(n+1,0);
     // 定义小根堆,用于存储未确定最短路的节点
     priority_queue<pair<double, int>, vector<pair<double, int>>, less<pair<double, int>>> pq;
     // 将源节点s加入小根堆
     pq.push(make_pair(1, s));
     dist[s]=1;
     while (!pq.empty()) {
         // 取出堆顶节点u
         int u = pq.top().second;
         pq.pop();
         //cout<<u<<' '<<to[u].size()<<endl;
         if(st[u])continue;
         st[u]=1;
         // 遍历u的邻接节点v
         for (auto& e : to[u]) {
             int v = e.first;
             double w = (double)(100-e.second)/100;
             // 如果u到v的距离更短,则更新dist[v]和prev[v]的值,并将v加入小根堆
             //cout<<u<<' '<<dist[u]*w<<' '<<dist[v]<<' '<<v<<endl;
             if (dist[u]*w > dist[v]) {
                 dist[v] = dist[u]*w;
                 pq.push(make_pair(dist[v], v));     
             }
         }
     }
     return dist;
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n,m,s;
     cin>>n>>m;
     vector<vector<pair<int,int>>>to(n+1);
     for (int i = 0; i < m; i++)
     {
         int x,y,w;
         cin>>x>>y>>w;
         to[x].push_back({y,w});
         to[y].push_back({x,w});
     }
     int ss;
     cin>>s>>ss;
     vector<double>ans=dijkstra(n,s,to);
     printf("%.8lf\n",100/ans[ss]);
     return 0;
 }

P1690 贪婪的Copy

题意:

在图上从起点开始,要经过p个点(可能包含起点、终点),再到终点的最短距离。n<=100,p<=10

题解:

和上面第二题一样,一条路上经过的相邻两点的最优情况就是两点的最短距离,我们用他们的最短距离来代替这p个点的距离,再求经过p个点的最短距离。

因为要找到经过所有点的路径,不满足最优子结构,是NP问题,只能硬搜,复杂度p!(所以起点、终点的情况要处理好,不然复杂度就从10!到11!或者12!了)。

所以floyd求所有边的距离,再在p个点和1,n中硬搜。

代码:

#include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
 void floyd(int n,vector<vector<int>>&dp){
     for (int i = 0; i < n; i++)
     {
         for (int j = 0; j < n; j++)
         {
             for (int k = 0; k < n; k++)
             {
                 dp[j][k]=min(dp[j][k],dp[j][i]+dp[i][k]);
             }
         }
     }
 }
 ​
 vector<vector<int>>dp;
 set<int>p;
 vector<int>st;
 int n;
 int ans=inf;
 void dfs(int pos,int val,int num){
     if(num==p.size()){
         ans=min(ans,val+dp[pos][n-1]);
         return;
     }
     else if(pos==n-1)
     {
         return;
     }
     for (auto i:p)
     {
         if(st[i]){
             st[i]=0;
             dfs(i,val+dp[pos][i],num+1);
             st[i]=1;
         }
     }
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     cin>>n;
     dp.assign(n,vector<int>(n));
     st.assign(n,1);
     for (int i = 0; i < n; i++)
     {
         for (int j = 0; j < n;  j++)
         {
             dp[i][j]=read();   
         }
     }
     
     floyd(n,dp);
     int m;
     cin>>m;
     int flag=0;
     for (int i = 0; i < m; i++)
     {
         int x=read()-1;
         p.insert(x);
     }
     p.insert(0);
     st[0]=0;
     dfs(0,0,p.find(0)!=p.end());
     cout<<ans<<endl;
     return 0;
 }

P1073 [NOIP2009 提高组] 最优贸易

题意:

给定一个有向图,其中一些边是双向的,每个节点都有一个权值。你需要从节点 1 出发,最终到达节点 n,可以重复经过任何节点。你可以选择在某个节点买入一个单位的商品,并在之后经过的另一个节点卖出这个单位的商品,用赚取的差价作为收益。你最多只能进行一次买卖。请计算你能获得的最大收益。

题解:

如果有环(包括双边),上面的权值可以随便选,可以用tarjan缩点成一个有向无环图,拓扑排序后dp,dp存到这个点可选的最大最小值。

但是tarjan忘了怎么写。

可以用分层图(每一层都是原图,自己建边连接每一层):用点的权值做层与层之间点i_1->i_2->i_3的权值,选择了一个点就进入了下一层,同一层的路径权值都是0,这些层就构成了路径问题(没有负环,一层上一层,最长、最短路径都有最优子结构,可以SPFA)(分层图第x层的i点是i+x*n,不是直接i*x,笨蛋)。

第一层从s开始选一条边(买入,负权值)进入第二层的对应的点,再选一个点卖出进入第三层,在第三层找到终点n

代码:

 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 vector<int>val;
 vector<int> SPFA(vector<vector<pair<int,int>>>&to,int s,int n){
     vector<int>dist(n,-inf);
     vector<bool>vis(n);//是否在队列中
     vis[s] = true;
     dist[s]=0;
     queue<int>q;
     q.push(s);
     while (!q.empty()) {
         int u = q.front();
         q.pop();
         vis[u] = false;
         for (auto e : to[u]) {
             int v = e.first, w = e.second;
             if (dist[v] < dist[u] + w){
                 dist[v] = dist[u] + w;
                 if (!vis[v]) {
                     vis[v] = true;
                     q.push(v);
                 }
             }
         }
     }
     return dist;
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n=read(),m=read();
     val.assign(n+1,0);
     for (int i = 1; i <= n; i++)
     {
         val[i]=read();
     }
     vector<vector<pair<int,int>>>to(3*n+1);
     for (int i = 0; i < m; i++)
     {
         int x=read(),y=read(),k=read();
         to[x].push_back({y,0});
         to[x+n].push_back({y+n,0});
         to[x+n+n].push_back({y+n+n,0});
 ​
         if(k==2){
             to[y].push_back({x,0});
             to[y+n].push_back({x+n,0});
             to[y+n+n].push_back({x+n+n,0});
         }
     }
     for (int i = 1; i <= n; i++)
     {
         to[i].push_back({i+n,-val[i]});
         to[i+n].push_back({i+n+n,val[i]});
     }
     vector<int>ans=SPFA(to,1,3*n+1);
     cout<<ans[3*n];
     return 0;
 }

P1608 路径统计

题意:

一个有向图,其中有 N 个节点和 E 条边。每条边从节点 I 到节点 J,花费为 C。你需要从节点 1 出发,到达节点 N,计算最少的花费以及花费最少的路径的总数。

坑:两个不同的最短路方案要求:路径长度相同(均为最短路长度)且最短路经过的点的编号序列不同,而数据提供的边信息可能会重复

题解:

一种常用的方法是使用动态规划。首先,使用 Dijkstra 算法或 Bellman-Ford 算法计算出两个节点之间的最短距离。然后,使用动态规划来计算最短路径的数量。设 dp[i] 表示从起点到节点 i 的最短路径数量,那么对于每条边 (u, v),如果 dis[u] + w(u, v) == dis[v],则 dp[v] += dp[u]。

我们要确定dp顺序,有环我们不能拓扑排序,SPFA就不行了,一个点,可能会更新好几次,边 (u, v)中,u的来源我们不能确定,如果是没有加过的边我们要dp[v] += dp[u],如果是已经加过的边,他更新了,我们不能知道它更新了多少,如图:

dijkstra每点就更新一次就可以。

代码:

 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
 vector<int>dp;
 vector<int> Dijkstra(vector<vector<pair<int,int>>>&to,int s,int n){
     // 初始化距离数组和前驱数组
     vector<int>st(n,0);
     vector<int>dist(n, inf);
     dp.assign(n,0);
     dp[s]=1;
     dist[s] = 0;
     // 定义小根堆,用于存储未确定最短路的节点
     priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
     // 将源节点s加入小根堆
     pq.push(make_pair(0, s));
     while (!pq.empty()) {
         // 取出堆顶节点u
         int u = pq.top().second;
         pq.pop();
         if(st[u])continue;
         st[u]=1;
         // 遍历u的邻接节点v
         for (auto& e : to[u]) {
             int v = e.first;
             int w = e.second;
             // 如果u到v的距离更短,则更新dist[v]的值,并将v加入小根堆
             if(dist[u]+w==dist[v])dp[v]+=dp[u];
             if (dist[u] + w < dist[v]) {
                 dist[v] = dist[u] + w;
                 dp[v]=dp[u];
                 pq.push(make_pair(dist[v], v));
             }
         }
     }
     return dist;
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n=read(),m=read();
     vector<vector<pair<int,int>>>to(n+1);
     vector<vector<int>>vis(n+1,vector<int>(n+1,-1));//有重边,去重
     for (int i = 0; i < m; i++)
     {
         int x=read(),y=read(),w=read();
         if(~vis[x][y]){if(w<to[x][vis[x][y]].second)to[x][vis[x][y]].second=w;continue;}//权值可能要更新
         vis[x][y]=to[x].size();
         to[x].push_back({y,w});
     }
     vector<int>ans=Dijkstra(to,1,n+1);
    
     if(dp[n])cout<<ans[n]<<' '<<dp[n];
     else cout<<"No answer";
     return 0;
 }
 

P1613 跑路

题意:

一个有向图,50个点,边权为距离1,每秒可以走2的任意次幂,求到终点的最短时间(二进制中 1 的个数最少的路径)

题解:

对于求二进制中 1 的个数最少的路径,没有最优子结构(应该与它不能线性积累有关)。实际上,这个所求的目标是间接来的,直接上是时间的最短路,我们处理一下权值就行。

代码中先处理1秒能到走的所有路径,通过dp处理,并建立了权值为时间的路径to,它有最优子结构,再用floyd求解。

代码:

 ​
 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
 ​
 void init(vector<vector<vector<bool>>>&dp,int n,vector<vector<int>>&to){
 for (int k = 1; k < 32; k++)
     for (int z = 0; z < n; z++)
         for (int i = 0; i < n; i++)
             for (int j = 0; j < n; j++)
                     dp[i][j][k]=dp[i][j][k]|(dp[i][z][k-1]&dp[z][j][k-1]);
 ​
     for (int i = 0; i < n; i++)
         for (int j = 0; j < n; j++)
             for (int k = 0; k < 32; k++)
                 if(dp[i][j][k])to[i][j]=1;
 }
 ​
 void floyd(vector<vector<int>>&to,int n){
     for (int k = 0; k < n; k++)  
         for (int i = 0; i < n; i++)
             for (int j = 0; j < n; j++)
                 to[i][j]=min(to[i][j],to[i][k]+to[k][j]);
 }
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n=read()+1,m=read();
     vector<vector<vector<bool>>>dp(n,vector<vector<bool>>(n,vector<bool>(32,0)));
     vector<vector<int>>to(n,vector<int>(n,inf));
     for (int i = 0; i < m; i++)
     {
         int x=read(),y=read();
         dp[x][y][0]=1;
     }
     init(dp,n,to);
     floyd(to,n);
     cout<<to[1][n-1];
     return 0;
 }

P5905 【模板】Johnson 全源最短路

题意:

给定一个包含 n 个结点和 m条带权边的有向图,求所有点对间的最短路径长度

  1. 边权可能为负,且图中可能存在重边和自环;

  2. 部分数据卡 n 轮 SPFA 算法。

    若图中存在负环,输出仅一行 −1。

    若图中不存在负环:

    输出 n 行:令 dis_{i,j} 为从 i 到 j 的最短路,在第 i行输出 \sum^n_{j=1}(j*dis_{i,j})注意这个结果可能超过 int 存储范围。

    如果不存在从 i 到 j 的路径,则 dis_{i,j}=10^9;如果 i=j,则 dis_{i,j}=0。

    1≤n≤3×10^3, 1≤m≤6×10^3, 1≤u,v≤n, −3×10^5≤w≤3×10^5

题解:

有负边,floyd的O(n^3)的复杂度太大,n次SPFA一般可以,但题目卡数据。

Dijkstra是肯定够的,我们怎么能处理边权都为正呢?这就是johnson的处理,"re-right"。

设原来的所有 i 到 j 的路径的集合为W_{i,j},那我们需要求的就是它的最小值。

我们先想一下,都加一个超级大的权值可以吗,然后记录最短路的边数,结果减去这个权值乘边数?

结果不行,我们想一下我们加了边权x后,我们求的是(W_{i,j}+L_{i,j}*x)的最小值,l(length)表示对应路径的边数。也就是说,我们加入了新的参数影响了求的最短路径(就是路径)。

johnson的操作是这样的:

先求一遍最短路f[ ],那么w_{u,v}+f_u \geq f_v,那么w_{u,v}+f_u -f_v\geq 0

那么我们把边权换成了w'_{u,v}=w_{u,v}+f_u -f_v,

dist′=weight(u,v_1)+f[u]−f[v_1]+weight(v_1,v_2)+f[v_1]−f[v_2]+...+weight(v_n,v)+f[v_n]−f[v]=weight(u,v_1)+weight(v_1,v_2)+...+weight(v_n,v)+f[u]−f[v]

=dist+f[u]−f[v]

f[u]-f[v]是只与起点、终点有关的常数,我们求的是(W_{i,j}+f[i]-f[j])的最小值,就是原来的路径。

为了让图连通,我们加入一个超级源点求f[],因为有负值,我们用SPFA,然后在原图上求n次Dijkstra,复杂度O(nm+n^2logm)。

虽然复杂度可能接近n次SPFA,但谁让SPFA不稳定可以被卡掉呢。

代码:

 #include <iostream>
 #include <fstream>
 #include <set>
 #include <vector>
 #include <algorithm>
 #include <cstdio>
 #include <utility>
 #include <map>
 #include <string.h>
 #include <cmath>
 #include <queue>
 #include <stack>
 #include <string>
 #include <bitset>
 #include <numeric>
 #define ll long long
 #define inf 1e9
 using namespace std;
 ​
 inline ll read() {ll x = 0, z = 1;char c = getchar();while (!isdigit(c)) {if (c == '-')z = -1;c = getchar();}while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return z * x;}
 inline void writ(ll x){if(x<0) {putchar('-');x=(~x)+1;}if(x>9)writ(x/10);putchar(x-x/10*10+48);}
 const int N=100010;
 ​
 bool SPFA(vector<vector<pair<int,int>>>&to,vector<int>&f,int s,int n){
     vector<bool>vis(n);//是否在队列里;
     queue<int>q;
     vector<int>cnt(n,0);
     q.push(s);
     vis[s]=1;
     f[s]=0;
 ​
     while(!q.empty()){
         int v=q.front();
         q.pop();
         vis[v]=0;
         if(cnt[v]>=n)return false;
         int u,w;
         for (auto e:to[v])
         {
             int u=e.first;
             int w=e.second;
             if(f[v]+w<f[u]){
                 f[u]=f[v]+w;
                 if(!vis[u]){q.push(u);vis[u]=1;cnt[u]++;}
             }
         }
     }
     return true;
 }
 ​
 vector<int> dijkstra(vector<vector<pair<int,int>>>&to,int s,int n){
     vector<int>dist(n,inf);
     vector<bool>vis(n,0);
     dist[s]=0;
 ​
     priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>pq;
     pq.push({0,s});
 ​
     while(!pq.empty()){
         int v=pq.top().second;
         pq.pop();
         if(vis[v])continue;
         vis[v]=1;
         for (auto& e:to[v])
         {
             int u=e.first,w=e.second;
             if(dist[v]+w<dist[u]){
                 dist[u]=dist[v]+w;
                 pq.push({dist[u],u});
             }
         }
     }
     return dist;
 }
 ​
 ​
 int main(int argc, char *argv[])
 {
     //ios::sync_with_stdio(false);
     //cin.tie(0);
     int n=read()+1,m=read();
     vector<vector<pair<int,int>>>to(n);
     vector<int>f(n,inf);
     vector<vector<int>>d(n,vector<int>(n));
     for (int i = 0; i < m; i++)
     {
         int x=read(),y=read(),w=read();
         to[x].push_back({y,w});    
     }
 ​
     //建立超级源点
     for (int i = 0; i < n; i++)
     {
         to[0].push_back({i,0});
     }
     
     //计算f[]
     if(!SPFA(to,f,0,n)){
         cout<<-1<<endl;
         return 0;
     }
     //去掉超级源点
     to[0].clear();
 ​
     //更新路径权值
     for (int i = 0; i < n; i++)
         for (auto&k:to[i])
             k.second+=f[i]-f[k.first];
     
     //求最短路,并修正
     vector<vector<int>>ans(n);
     for (int i = 1; i < n; i++)
     {
         ans[i]=dijkstra(to,i,n);
         for (int j = 1; j < n; j++)
             if(ans[i][j]!=inf)ans[i][j]-=f[i]-f[j];//如果不存在边就不修正;
 ​
         // for (int j = 0; j < n; j++)
         // {
         //     cout<<ans[i][j]<<" \n"[j==n-1];
         // }
           
     }
 ​
     for (int i = 1; i < n; i++)
     {
         ll sum=0;
         for (int j = 1; j < n; j++)
         {
             sum+=(ll)ans[i][j]*j;
         }
         cout<<sum<<endl;
     }
     return 0;
 }

更新中...

P1099 [NOIP2007 提高组] 树网的核

P2169 正则表达式

P2407 [SDOI2009]地图复原

P2371 [国家集训队]墨墨的等式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值