图论
最短路
单源最短路径
Dijkstra算法
该算法基于贪心算法,优点是复杂度保证,但它只适用于非负权图(有负边权这个贪心就假了),时间复杂度\(O(nlogm)\)
code
int n,m,s,dis[N];//s为起始点
struct edge {int to,w;};
vector<edge>e[N];
bool vis[N];
void dij() {
priority_queue<pair<int,int>>q;
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push({0,s});
while(q.size()) {
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
for(edge T:e[x]) {
int y=T.to,w=T.w;
if(dis[y]>dis[x]+w) dis[y]=dis[x]+w,q.push({-dis[y],y});
}
}
}
spfa算法
该算法通过不断松弛已更新的节点去更新其他节点,优点是可以应对一些有负边权的图(但是遇到负环不断松弛就假了),时间复杂度为\(O(km)\),\(k\)是一个较小的常数,但是复杂度不保证,有可能退化为\(O(nm)\)
code
int n,m,s,dis[N];//s为起始点
struct edge {int to,w;};
vector<edge>e[N];
bool vis[N];
void spfa() {
queue<int>q;
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(s);
while(q.size()) {
int x=q.front();
q.pop();
vis[x]=0;
for(edge T:e[x]) {
int y=T.to,w=T.w;
if(dis[y]>dis[x]+w) {
dis[y]=dis[x]+w;
if(!vis[y]) q.push(y),vis[y]=1;
}
}
}
}
spfa判负环
若一个点的最短路经过了\(n\)个点以上,就说明spfa遇到负环然后死了,代码很简单,spfa的基础上判一下就可以
code
bool spfa() {
memset(dis,0x3f,sizeof(dis));
queue<int>q;
q.push(1);
cnt[1]=1,dis[1]=0;
while(q.size()) {
int x=q.front();
q.pop();
vis[x]=0;
for(int i=h[x];i;i=e[i].nxt) {
int y=e[i].to,val=e[i].val;
if(dis[y]>dis[x]+val) {
cnt[y]=cnt[x]+1;
if(cnt[y]>n) return 1;
dis[y]=dis[x]+val;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
return 0;
}
任意两点间最短路径
Floyd算法
该算法可以在\(O(n^{3})\)的复杂度里求最短路
code
void Floyd() {
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]) dis[i][j]=a[i][j];
//k是状态,要放在最外层
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
图论建模
例题:CSP模拟11-传送门
题目大意
有一个迷宫,里面放着一个蛋糕,你想去把这个蛋糕吃掉。
你有一张迷宫的地图,这张地图有 RR 行,每行有 CC 个方格。
每个方格中有一个字符,字符有四种:
# (井字号)表示墙。
. (点号)表示空地。
S (大写字母S)表示你开始的位置。
C (大写字母C)表示蛋糕的位置。
你只能在空地上行走,从一个空地走到另一个相邻的空地。
此外,地图上所描绘的矩形区域完全被墙壁所包围。
为了尽快地吃到蛋糕,你已经从 Aperture Science™ 那里获取了一支传送枪,其功能如下:
在任何时候,它都可以向上、左、下、右四个方向中的一个发射传送门。当一个传送门被发射,它会一直向发射的方向飞行,直到碰触到墙壁。这时,传送门会被放置在这堵墙上。
只允许有两座传送门同时存在,如果已经有两个传送门被放置在迷宫里,那么其中一个(由您选定)将在再次使用传送枪时被立即移除。在一个现有的门上放置传送门将代替原来的传送门(在墙壁的每一侧最多有一座传送门)注意,在同一堵墙壁的不同侧面可能有两个传送门。
当有两个传送门同时存在时,你可以使用它们来传送自己。当你位于一个传送门前时,你可以进入这个传送门并到达另一个传送门所在的位置,传送和移动一格同样消耗一个单位时间。
你可以假设进出传送门不需要时间,并且在两个相邻的方块之间移动或通过传送门传送需要一个时间单位。
给出迷宫的地图以及你的起始位置和蛋糕的位置,计算出你吃到蛋糕所需的最短时间
解题思路
首先明确一点,进入当前位置发射的传送门一定比进入之前位置发射的传送门更优(可以手模一下)
这样,我们在一个点想要传送,就必须从离他最近的传送门过去
因此我们先将空地与空地连边,\(val=1\)
设走到距当前位置最近的传送门的最短距离为\(minval\)
将当前位置与其他传送门连边,\(val=minval+1\)
再跑一遍\(dijkstra\)就好啦
时间复杂度\(O(n^{2}logn)\),空间复杂度\(O(n^{2})\)
code
#include<bits/stdc++.h>
#define inf INT_MAX
using namespace std;
const int N=1010;
int n,m,sx,sy,tx,ty,d[N][N],dis[N][N];
int idx[10]={0,-1,0,1,0};
int idy[10]={0,0,1,0,-1};
char a[N][N];
bool vis[N][N];
struct node {
int x,y,val;
bool operator<(const node nd) const {return val>nd.val;}
};
vector<node>to[N][N];
void dij() {
memset(dis,0x3f,sizeof(dis));
dis[sx][sy]=0;
priority_queue<node>q;
q.push({sx,sy,0});
while(q.size()) {
node nw=q.top();
q.pop();
if(vis[nw.x][nw.y]) continue;
vis[nw.x][nw.y]=1;
//走路
for(int i=1;i<=4;i++) {
int x=nw.x+idx[i],y=nw.y+idy[i];
if(x<1||x>n||y<1||y>m||a[x][y]=='#') continue;
if(dis[x][y]>dis[nw.x][nw.y]+1) dis[x][y]=dis[nw.x][nw.y]+1,q.push({x,y,dis[x][y]});
}
//传送
for(auto T:to[nw.x][nw.y]) {
int x=T.x,y=T.y;
if(dis[x][y]>dis[nw.x][nw.y]+d[nw.x][nw.y]+1)
dis[x][y]=dis[nw.x][nw.y]+d[nw.x][nw.y]+1,q.push({x,y,dis[x][y]});
}
}
}
int main() {
freopen("portals.in","r",stdin);
freopen("portals.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
cin>>a[i][j];
if(a[i][j]=='S') sx=i,sy=j;
if(a[i][j]=='C') tx=i,ty=j;
}
//预处理最短距离和传送门位置
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
if(a[i][j]=='#') continue;
int k,minval=inf;
for(k=i;k&&a[k][j]!='#';k--);
to[i][j].push_back({k+1,j});
minval=min(minval,i-k-1);
for(k=i;k<=n&&a[k][j]!='#';k++);
to[i][j].push_back({k-1,j});
minval=min(minval,k-i-1);
for(k=j;k&&a[i][k]!='#';k--);
to[i][j].push_back({i,k+1});
minval=min(minval,j-k-1);
for(k=j;k<=m&&a[i][k]!='#';k++);
to[i][j].push_back({i,k-1});
minval=min(minval,k-j-1);
d[i][j]=minval;
}
dij();
cout<<dis[tx][ty];
return 0;
}
最小生成树
Kruskal算法
在任意时刻,维护无向图的最小生成森林,从剩余边中选出最小的,若此边的两端点所属树不连通,则用该边将两棵树联通,若两端点联通,则无需加入(比较显然),图中点的情况可以用并查集维护,时间复杂度\(O(mlogm)\)
例题:P3366 【模板】最小生成树
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,fa[N],ans;
struct edge {
int x,y,val;
bool operator<(const edge z) const {return val<z.val;}
}e[N];
int find(int x) {
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
signed main() {
cin>>n>>m;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,x,y,z;i<=m;i++) {
cin>>x>>y>>z;
e[i]={x,y,z};
}
sort(e+1,e+1+m);
for(int i=1;i<=m;i++) {
int x=e[i].x,y=e[i].y,z=e[i].val;
x=find(x),y=find(y);
if(x==y) continue;
ans+=z;
fa[x]=y;
}
for(int i=2;i<=n;i++) if(find(i)!=find(i-1)) return (cout<<"orz"),0;
cout<<ans;
return 0;
}

浙公网安备 33010602011771号