类型
一.稍微变形的模板
例:1.P1576 最小花费
松弛操作改了一点点。
点击查看代码
if(dis[v]>dis[u]/(1-0.01*w)){
dis[v]=dis[u]/(1-0.01*w);
q.push(make_pair(dis[v],v));
}
2.P3905 道路重建
把好路的边权设置为0。然后跑最短路就行了。
3.P1342 请柬
正反各一遍最短路(好多题都是这个套路,关键是想出来)。
二.最短路径树
这篇博客讲的不错:最短路径树。
还是口胡一下吧。如果讲的哪里有问题,欢迎指出,感谢。
概念
在一张无向边带权图的基础上,构建一棵树,使根节点到树上任意节点的距离都是原图中根节点到该点的最短路。
原理
(即建树原理),一个点到根节点的最短路一定由其他点到根节点的最短路或者直接由根转移过来的。所以一张图就这样构成一棵树了。
操作
与单源最短路模板相比,每个点有一个前继节点,只需在松弛的时候根据题意更换前继节点,就像这样。
点击查看代码
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pre[v]=u;
q.push(make_pair(dis[v],v));
}else if(dis[v]==dis[u]+w){
if(pre[v]>u) pre[v]=u;
}
例:
1.Paths and Trees
要求边权和最小的最短路径树,即使得该最短路径树的边权和最小。
假设有两条到 \(v\) 的最短路,但是直接和 \(v\) 相连的边权不一样,该找哪一条呢?如果听不明白,这有张图。
可以看出选连接 \(v\) 的边权最小的那条边。于是 else if 里的那一段就可以写出来了。
核心代码:
点击查看代码
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pre_edge[v]=id;
pre_dis[v]=w;
q.push(make_pair(dis[v],v));
}else if(dis[v]==dis[u]+w&&pre_dis[v]>w){
pre_edge[v]=id;
pre_dis[v]=w;
}
2.P5201 [USACO19JAN]Shortcut G
题意:给定一个有 \(n\) 个结点和 \(m\) 条边的带权无向图,每个结点 \(i\) 上有 \(c_i\) 头奶牛,\(1\)号结点为家,每次回家奶牛会走一条最短的路径,如果有多条长度相同的最短路径,则奶牛会走字典序最小的一条。现在,你可以增加一条从\(1\)到任意结点的长度为给定值 \(T\) 的一条边。如果一头奶牛在平时回家的路上经过了这条边相连的结点,且这条边能使其回家路径更短,则其会走这条边。求能使所有奶牛走的路径长度和的变化的最大值。(来自hsfzLZH1大佬的概括)
思路:可以发现,需要将每个节点上的奶牛到根走的路径求出来,然后枚举要在哪个点建立路径。
可以将以\(1\)为根的最短路径树建出来,这是一棵什么样的最短路径树呢?
我不会说,核心代码:
点击查看代码
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pre[v]=u;
q.push(make_pair(dis[v],v));
}else if(dis[v]==dis[u]+w){
if(pre[v]>u) pre[v]=u;//因为要走字典序最小的路径,所以当之前确定的点大于现在可以松弛的点时,说明就得替换前继节点。
}
点击查看代码
for(int i=2;i<=n;i++)
ans=max(ans,1ll*(dis[i]-t)*siz[i]);
剩下一道例题(可以自己去看看):Berland and the Shortest Paths
大概总结一下:其实核心部分还是找前继节点(也就是 else if 那一段),根据题目要求建树就好了。
三.分层图
关于讲解:楠不会(依旧引荐其他大佬的博客)分层图。
例题:1.P4568 [JLOI2011] 飞行路线
可以发现 \(k\) 很小,\(n\) 也不大,我们可以建20层一模一样的图,在每一层之间建免费的边。这样最多使用20次免费的机会,也就是20层,跑最短路即可。
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#define pii pair<long long,int>
using namespace std;
vector<pii> g[200005];
priority_queue<pii,vector<pii>,greater<pii> > q;
int n,m,k,s,t;
bool vis[200005];
long long v[200005];
long long minn=0x7fffffff;
void dij(int s){
v[s]=0;
q.push(make_pair(v[s],s));
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]){
continue;
}
vis[u]=1;
for(int i=0;i<g[u].size();i++){
int uu=g[u][i].second;
int vv=g[u][i].first;
if(vis[uu]) continue;
if(v[uu]>v[u]+vv){
v[uu]=v[u]+vv;
q.push(make_pair(v[uu],uu));
}
}
}
}
int main(){
cin>>n>>m>>k>>s>>t;
for(int i=0;i<=n*(k+1);i++){
v[i]=0x7fffffff;
}
for(int i=1;i<=m;i++){
int from,to,dis;
cin>>from>>to>>dis;
g[from].push_back(make_pair(dis,to));
g[to].push_back(make_pair(dis,from));
for(int j=1;j<=k;j++){
g[from+j*n].push_back(make_pair(dis,to+j*n));
g[to+j*n].push_back(make_pair(dis,from+j*n));
g[from+(j-1)*n].push_back(make_pair(0,to+j*n));
g[to+(j-1)*n].push_back(make_pair(0,from+j*n));
}
}
dij(s);
for(int i=0;i<=k;i++){
minn=min(minn,v[t+i*n]);
}
cout<<minn;
return 0;
}
三倍经验:
P4822 [BJWC2012]冻结
P2939 [USACO09FEB]Revamping Trails G
四.线段树优化建图
Legacy
题意:一张有向边带权图,三种操作:
操作1
:从 \(u\) 到 \(v\) 连一条长度为 \(w\)的边。
操作2
: 从 \(v\) 向 \([l,r]\) 这个区间内的点连长度为 \(w\) 的边。
操作3
:从 \([l,r]\) 这个区间内的点向 \(v\) 连长度为 \(w\) 的边。
最后求 \(s\) 到其他点的最短路长度。
emm...先鸽了吧。