最短路
最短路
其实最短路来自图论 , 所以先不讲最短路 , 先来讲讲关于图的基本知识
存图
链式前向星
好简单直接过
但是!!!如果是无向边数组记得开双倍空间呀!
放个代码:
struct egde{
int x, y, z, next;
}e[maxm<<1];//无向图开双倍空间
int head[maxn], vis[maxn], dis[maxn], cnt;
inline void add_edge( int x , int y , int z ){
e[++cnt].x = x , e[cnt].y = y , e[cnt].z = z;
e[cnt].next = head[x];
head[x] = cnt;
}
for(int i = head[now]; i; i = e[i].next){
/*do something*/
}
我个人比较喜欢用vector存图
邻接矩阵
这个更暴力了,所以不讲。这里讲一个队邻接矩阵的优化:
用vector实现邻接矩阵(其实这个时候已经叫领接表了)STL大法好
代码:
vector<pair<int, int> > v[maxn];
void add_edge(int x, int y, int z){
return (void)(v[x].push_back(make_pair(y,z)), v[y].push_back(make_pair(x,z)));
}
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i].first, len = v[now][i].second;
/*do someting*/
}
多好
图的遍历
基于栈的DFS
象征性放一下代
void dfs(int now){
vis[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(!vis[to]) dfs(to);
}
return;
}
基于队列的BFS
代:
void bfs(int x){
queue<int> q;
q.push(x), vis[x] = 1;
while(q.size()){
int now = q.front();
q.pop();
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(!vis[to]) q.push(to), vis[now] = 1;
}
}
return;
}
最短路算法
SPFA
用于求解单源最短路问题,即求-一个点到图上其它点的最短路。
是\(Bellman-Ford\)的优化。
算法描述:
- 对每个点\(x\)设置一个变量\(dis[x]\), 表示它离起点8的最短距离,令\(dis[x]= 0\)。
- 维护一个队列,先将起点入队。
- 每次从队列中取出一个点,用它去更新所有相邻的点。
- \(dis[to] = min(dis[to],~dis[now] + len)\)。
- 在一个点被更新后,如果它不在队列中,则将它入队。
时间复杂度上界仍然为\(O(nm)\),但对于随机数据,该算法效率非常高。
说人话:SPFA非常非常非常容易被卡爆qwq。
代码:
void SPFA(int x){
queue<int> q;
q.push(x), vis[x] = 1, dis[x] = 0;
while(q.size()){
int now = q.front();
q.pop(), vis[now] = 0;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i].first, len = v[now][i].second;
if(dis[to] > dis[now] + len){
dis[to] = dis[now] + len;
if(!vis[to]){
q.push(to);
vis[to] = 1;
}
}
}
}
return;
}
Dijkstra
用于求单源最短路问题
算法描述:
- 将点分为两类, 一类是最短路已确定的点,另一类是最短路未确定的点。
- 对每个点\(x\)设置一个变量\(dis[x]\),表示它离起点的最短距离。
- 首先将起点\(x\)标记为最短路已确定的点,即\(vis[x]=1\),并且\(dis[x]= 0\)。
- 每次从所有最短路未确定的点中取出离起点最近的点,将它标记为最短路已经确定,用它去更新与它相邻的所有点。
- \(dis[to] = min(dist[to],~dis[now] + len)\)
- 时间复杂度为\(O(n^2)\)。
堆优化:使用链式前向星或邻接矩阵存图,用堆来优化寻找最近点的过程,时间复杂度降至\(O(mlogn)\)。
Dijkstra不会被卡,因为他的时间复杂度是严格的\(O(mlogn)\)。
注意啦:因为Dijkstra的算法原理是贪心,而出现负边权的话我们显然可以发现贪心是错误的,所以Dijkstra无法解决带有负权值得最短路问题。
这里只提供\(O(mlogn)\)的写法。
代码:
void Dijkstra(){
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > q;
q.push(make_pair(0,1));
dis[1] = 0;
while(q.size()){
int now = q.top().second, len = q.top().first;
q.pop();
if(vis[now]) continue;
vis[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i].first, weight = v[now][i].second;
if(dis[to] > len + weight){
dis[to] = len + weight;
q.push_back(make_pair(dis[to],to));
}
}
}
return;
}
Floyd
挺简单的我也懒得打字了,听我口胡。
代码:
void Floyd(){
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i != j && j != k && k != i && dis[i][j] > dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
return;
}
最短路径输出
没用的小知识qwq。
在更新最短路的时候用\(pre\)数组记录前驱即可。
代码:
void print(int n){
if(!pre[n]) return;
print(pre[n]);
printf("%d", n);
}
最短路计数
上模板。
这个点的路径数可以由上一个点转移过来。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10, mod = 100003;
vector<int> v[maxn];
int n, m, dis[maxn], vis[maxn], sum[maxn];
void BFS(int x){
queue<int> q;
q.push(x), vis[x] = 1, dis[x] = 0;
while(q.size()){
int now = q.front();
q.pop();
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(dis[to] > dis[now] + 1){
dis[to] = dis[now] + 1;
sum[to] = sum[now];
vis[to] = 1;
q.push(to);
}else if(vis[to] and dis[to] == dis[now] + 1){
sum[to] += sum[now];
sum[to] %= mod;
}
}
}
}
signed main(){
memset(dis, 0x3f, sizeof(dis));
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++){
sum[i] = 1;
}
for(int i = 1, x, y; i <= m; i ++){
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
BFS(1);
for(int i = 1; i <= n; i ++){
if(vis[i]) printf("%d\n", sum[i]);
else printf("0\n");
}
return 0;
}
分层最短路
分成最短路其实就是把一个图分层然后我们来跑一遍最短路。通俗易懂的语言==废话
分层最短路的关键在于我们如何去给这个图分层。
以题为例。 是道裸题
我们会发现这道题很熟悉,所以仔细观察,我们还会发现\(k\)非常的小。
我们考虑如何给图分层,首先我们把原来的图原封不动的复制\(k\)遍(具体实现过程并不是这样的,这样讲方便理解)。
每一层之间我们以权值为\(0\)的边连接.....我也口胡不清楚,上图。
以样例为例(图来自luogu)
首先我们把\(n\)个点变为了\(n+n \times k\)个点,其中每\(i\)到\(i+n-1\)为一层好吧。
层与层之间的连接:如果\(x\)这个点到\(y\)这个点在原图上有边,那我们便在\(x+n \times (i-1)\)到\(y+n \times i\)上连一条边权为\(0\)的边表示使用免费次数,以此类推....
这样下来我们的图便分好层了。
分层的意义在于我从\(x\)点到\(y\)点的最短路径上可以保证使用的免费飞行次数$ \leq k$次,但肯定是使用的次数越多优的qwq。
有个小细节需要注意,我们分层最短路相当于把原来的图扩大了,所以我们的数组也要开大亿点点,差不多是到\(n+n \times k\),其实只要你不\(MLE\)你弄成\((n+n \times k) \times 10\)也没有问题,只要自己代码不会出锅就行╮(─▽─)╭
具体实现方式看代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
int n, m, k, posx, posy, dis[maxn], vis[maxn];
vector<pair<int,int> > v[maxn];
inline void add_edge(int x, int y, int z){ return (void)v[x].push_back(make_pair(y,z)); }
void Dijkstra(int x){
memset(dis, 0x3f, sizeof(dis));
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > q;
q.push(make_pair(0,x));
dis[x] = 0;
while(q.size()){
int now = q.top().second, len = q.top().first;
q.pop();
if(vis[now]) continue;
vis[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i].first, weight = v[now][i].second;
if(dis[to] > weight + len){
dis[to] = weight + len;
q.push(make_pair(dis[to],to));
}
}
}
return;
}
signed main(){
scanf("%d%d%d%d%d", &n, &m, &k, &posx, &posy);
for(int x, y, z; m; m --){
scanf("%d%d%d", &x, &y, &z);
add_edge(x, y, z);
add_edge(y, x, z);
for(int j = 1; j <= k; j ++){
add_edge(x+(j-1)*n, y+j*n, 0);
add_edge(y+(j-1)*n, x+j*n, 0);
add_edge(x+j*n, y+j*n, z);
add_edge(y+j*n, x+j*n, z);
}
}
for(int i = 1; i <= k; i ++) add_edge(posy+(i-1)*n, posy+i*n, 0);
Dijkstra(posx);
printf("%d", dis[posy+k*n]);
return 0;
}
做亿点题
衡水某二中集训的考试题
Solution
二分答案。听我口胡。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000+10;
vector<pair<int,int> >adj[maxn];
vector<pair<int,int> >temp_adj[maxn];
int dis[maxn], n, m, k;
inline void addEdge(int u,int v,int w){
adj[u].push_back(make_pair(v,w));
adj[v].push_back(make_pair(u,w));
return;
}
bool check(int ans){
for(int i = 1;i<=n;i++){
temp_adj[i].clear();
}
for(int i = 1;i<=n;i++){
for(int j = 0;j<adj[i].size();j++){
if(adj[i][j].second <= ans){
temp_adj[i].push_back(make_pair(adj[i][j].first,0));
}else{
temp_adj[i].push_back(make_pair(adj[i][j].first,1));
}
}
}
memset(dis,0x3f,sizeof(dis));
deque<int>q;
dis[1] = 0;
q.push_back(1);
while(q.size()){
int f = q.front();
q.pop_front();
for(int i = 0;i<temp_adj[f].size();i++){
int to = temp_adj[f][i].first,weight = temp_adj[f][i].second;
if(dis[to] > dis[f] + weight){
dis[to] = dis[f]+weight;
if(weight == 0){
q.push_front(to);
}else{
q.push_back(to);
}
}
}
}
if(dis[n] > k) return false;
else return true;
}
int main(){
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
addEdge(u,v,w);
}
int l = 0,r = 1000000;
while(l<r){
int mid = (l+r)/2;
if(check(mid)) r = mid;
else l = mid+1;
}
if(check(r)) cout<<r<<endl;
else cout<<-1<<endl;
return 0;
}
转化为图论模型
主要是看题。
P1854摆花
不是因为我不会动规才转化成最短路的。
Solution
听我口胡。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 500;
queue<int> q;
int f, V, comp, ans, ansd, cnt, dis[maxn*maxn], vis[maxn*maxn], pre[maxn*maxn], head[maxn*maxn];
pair<int, int> temp[maxn][maxn];
struct edge{
int to, length, next;
inline void push(int x, int y, int z, int cnt){
to = y, length = z, next = head[x];
head[x] = cnt;
return;
}
edge(){to = length = next = 0; return;}
}v[maxn*maxn];
inline void add_edge(int x, int y, int z){
++ cnt;
v[cnt].push(x,y,z,cnt);
return;
}
void SPFA(int x){
memset(pre, 0, sizeof(pre));
memset(dis, 128, sizeof(dis));
q.push(x);
vis[x] = 1, dis[x] = 0;
while(q.size()) {
int now = q.front();
q.pop();
vis[now] = 0;
for(int i = head[now]; i; i = v[i].next){
int to = v[i].to, len = v[i].length;
if(dis[to] < dis[now] + len){
dis[to] = dis[now] + len;
pre[to] = now;
if(!vis[to]){
q.push(to);
vis[to] = 1;
}
}
}
}
return ;
}
void print(int x, int high){
if(!x) return;
print(pre[x], high-1);
printf("%d ", x - high*V);
}
signed main(){
scanf("%d%d",&f,&V);
for(int i = 1; i <= f; i ++)
for(int j = 1; j <= V; j ++){
scanf("%d",&temp[i][j].first);
temp[i][j].second = ++comp;
}
for(int i = 1; i <= V-f+1; i ++) add_edge(0,temp[1][i].second,temp[1][i].first);
for(int k = 1; k < f; k ++)
for(int i = k; i <= V-f+k; i ++)
for(int j = i + 1; j <= V-f+k+1; j ++)
add_edge(temp[k][i].second,temp[k+1][j].second,temp[k+1][j].first);
SPFA(0);
for(int i = f*V-V+1; i <= comp; i ++)
if(dis[i] > ans) ans = dis[i], ansd = i;
printf("%d\n", ans);
print(ansd,f-1);
return 0;
}
AcWing177电路维修
Solution
建图:如果电线是 \ 就 从右上向左下连边,长为 1,从左上向右下连边,长为 0,意义是改变电线的方向代价是1,不改变就是0,之后跑最短路就行。