次小生成树:第二小的生成树。
次小生成树:删掉一条边,再加上一条边,使得差值尽量小,并且要是一个树。
次小生成树:如果一条边在最小生成树上,我们就叫他树边,如果不在最小生成树上就叫他非树边。
次小生成树:删掉一条树边,加上一条非树边。
次小生成树:倍增 LCA 询问环上最大的值(章鱼图)。
从一张 m 条边的图中找 n−1 条边,使得找出来的边和已有的点构成一棵树,组成的图就叫做原图的生成树。一个生成树的大小是选出来的所有边的边权之和。大小最小的生成树被称为 最小生成树。
生成树并查集的路径压缩
点击查看代码
//O(mlogm) sort最慢
#include<bits/stdc++.h>
using namespace std;
int to[MAXN];//to[i] 表示 i 点在并查集里面的箭头指向谁
int go(int p){//看一下点 p 沿着并查集箭头最后会走到哪里
//O(1)
if(to[p]==p)return p;//指向自己
/*else{
int q=go(to[p]);
to[p]=q;
return q;
}*/
else return to[p]=go(to[p]);
}
struct edge{
int s,e,d;
}ed[MAXN];//ed[i] 代表第 i 条边是在 s 与 e 之间的长度为 d 的边
int n,m;
bool cmp(edge a,edge b){
return a.d<b.d;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>ed[i].s>>ed[i].e>>ed[i].d;
sort(ed+1,ed+m+1,cmp);//所有边按照边权进行排序
for(int i=1;i<=n;i++)
to[i]=i;//初始化并查集
int ans=0;//生成树大小
int cnt=0;//边的数量
for(int i=1;i<=m;i++){//O(m)
if(cnt==n-1)break;
int p1=ed[i].s,p2=ed[i].e,d=ed[i].d;
if(go(p1)!=go(p2)){//不在同一个连通块
ans+=d;
to[go(p1)]=go(p2);
++cnt;
}
}
cout<<ans<<endl;
}
SPFA
点击查看代码
#include<bits/stdc++.h>
using namespace std;
vector<pair<int,int> > z[500005];
int dist[500005];//dist[i] 代表从起点到 i 的最短路
bool vis[500005];//vis[i]代表 i 在不在队列里
void add_edge(int s,int e,int d){
z[s].push_back(make_pair(e,d));
}
int n,m,x;
void SPFA(int s){//以 s 作为起点算最短路
memset(dist,0x3f,sizeof(dist));
dist[s]=0;
queue<int> q;//用来存可能改变其他点最短路的点
q.push(s);
vis[s]=true;
while(!q.empty()){
int p=q.front();
q.pop();
vis[p]=false;
for(int i=0;i<z[p].size();i++){
int e=z[p][i].first;
int d=z[p][i].second;
if(dist[e]>dist[p]+d){
dist[e]=dist[p]+d;
if(!vis[e])q.push(e),vis[e]=true;
}
}
}
}
signed main(){
cin>>n>>m>>x;
for(int i=1;i<=m;i++){
int s,e,d;
cin>>s>>e>>d;
add_edge(s,e,d);
}
SPFA(x);
for(int i=1;i<=n;i++){
cout<<dist[i]<<' ';
}
return 0;
}
生成树
点击查看代码
//O(nm)
#include<bits/stdc++.h>
using namespace std;
int to[MAXN];//to[i] 表示 i 点在并查集里面的箭头指向谁
int go(int p){//看一下点 p 沿着并查集箭头最后会走到哪里
if(to[p]==p)return p;//指向自己
else return go(to[p]);//递归调用
}
struct edge{
int s,e,d;
}ed[MAXN];//ed[i] 代表第 i 条边是在 s 与 e 之间的长度为 d 的边
int n,m;
bool cmp(edge a,edge b){
return a.d<b.d;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>ed[i].s>>ed[i].e>>ed[i].d;
sort(ed+1,ed+m+1,cmp);//所有边按照边权进行排序
for(int i=1;i<=n;i++)
to[i]=i;//初始化并查集
int ans=0;//生成树大小
int cnt=0;//边的数量
for(int i=1;i<=m;i++){
if(cnt==n-1)break;
int p1=ed[i].s,p2=ed[i].e,d=ed[i].d;
if(go(p1)!=go(p2)){//不在同一个连通块
ans+=d;
to[go(p1)]=go(p2);
++cnt;
}
}
cout<<ans<<endl;
}
Bellman_ford
点击查看代码
//可以针对负数边权,但是更慢。
#include<bits/stdc++.h>
using namespace std;
int s[100005],d[1000005],e[1000005];
int n,m;
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>s[i]>>e[i]>>d[i];
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=1;i<n;i++)
for(int j=1;j<=m;j++)
dist[e[j]]=min(dist[e[j]],dist[s[j]]+d[j]);
return 0;
}
Dijkstra优化
点击查看代码
//用堆
#include<bits/stdc++.h>
using namespace std;
vector<pair<int,int>> z[100005];
int dist[100005];//dist[i] 代表从起点到 i 的最短路
bool vis[i];//vis[i]代表 i 的最短路有没有被求出来
void add_edge(int s,int e,int d){
z[s].push_back(make_pair(e,d));
}
int n,m;
void Dijkstra(int s){//以 s 作为起点算最短路
memset(dist,0x3f,sizeof(dist));
dist[s]=0;
priority_queue<pair<int,int> > heap;
//first 用来存距离的相反数
//second 用来存点的编号
for(int i=1;i<=n;i++)
heap.push(make_pair(-dist[i],i));
for(int i=1;i<=n;i++){
//选一个 dist 最小的点
while(vis[heap.top().second])
heap.pop();
int p=heap.top().second;
heap.pop();
vis[p]=true;
//用这个点去进行松弛操作
for(int j=0;j<z[p].size();j++){
int q=z[p][j].first;
int d=z[p][j].second;///这是一条从 p 到 q 长度为 d 的边
if(dist[q]>dist[p]+d){
dist[q]=dist[p+d];
heap.push(make_pair(-dist[q],q));
}
}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int s,e,d;
cin>>s>>e>>d;
add_edge(s,e,d);
}
Dijkstra(1);
int T;
cin>>T;
while(T--){
int x;
cin>>x;
cout<<dist[x]<<'\n';
}
return 0;
}
拓扑排序
点击查看代码
//拓扑排序:针对 DAG。
//对有向图中的每一个点进行排序,使得最后的排序结果都是从左向右指的。
//不断删除入度为 0 的点,然后不断把入度为 0 的点加入答案。
//那如何删除一个点呢?其实不需要删除,只需要将入度减掉即可。
#include<bits/stdc++.h>
using namespace std;
vector<pair<int,int>> z[100005];
//z[i] 代表所有以 i 为起点的边
//z[i][j] 代表以 i 为起点的第 j 条边
//z[i][j].first 代表以 i 为起点的第 j 条边的终点
//z[i][j].second 代表以 i 为起点的第 j 条边的长度
void add_edge(int s,int e,int d){//添加一个从 s 出发到 e 的长度为 d 的边
//push_back:向 vector 的末尾加入一个元素
z[s].push_back(make_pair(e,d));
}
int n,m;
int in[100005];//in[i] 代表 i 点的入度
int result[100005];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int s,e,d;
cin>>s>>e>>d;//起点为s,终点为e,长度为d
add_edge(s,e,d);
in[e]++;
}
int cnt=0;
for(int i=1;i<=n;i++)
if(in[i]==0)result[++cnt]=i;
for(int i=1;i<=n;i++){//当前要取出拓扑排序的结果中的第 i 个点
int p=result[i];
for(int j=0;j<z[p].size();j++){
int q=z[p][j];//p->q的边
in[q]--;
if(in[q]==0)result[++cnt]=q;
}
}
}
多源最短路算法floyd原始版本
点击查看代码
//O(n^3) n<=250
#include<bits/stdc++.h>
using namespace std;
int dist[1005][1005][1005];
//dist[i][j][k] 代表从 j 走到 k 使得中间经过的节点编号 <=i 的情况下的最短路
const int INF=0x3f3f3f3f;
int n,m;//n 点 m 边
signed main(){
memset(dist,0x3f,sizeof(dist));//把数组每一个元素赋值为INF
cin>>n>>m;
for(int i=1;i<=m;i++){
int s,e,d;
cin>>s>>e>>d;//一条从 s 到 e 长度为 d 的边
dist[0][s][e]=min(dist[0][s][e],d);
}
for(int i=1;i<=n;i++)
dist[0][i][i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
dist[i][j][k]=min(dist[i-1][j][k],dist[i-1][j][i]+dist[i-1][i][k]);
int T;
cin>>T;
while(T--){
int i,j;
cin>>i>>j;
cout<<dist[n][i][j]<<'\n';
}
return 0;
}
多源最短路算法floyd压维
点击查看代码
//O(n^3) n<=250
//注意是无向图,所以存图时要存两次。
#include<bits/stdc++.h>
using namespace std;
int dist[1005][1005];
//dist[i][j][k] 代表从 j 走到 k 使得中间经过的节点编号 <=i 的情况下的最短路
const int INF=0x3f3f3f3f;
int n,m;//n 点 m 边
signed main(){
memset(dist,0x3f,sizeof(dist));//把数组每一个元素赋值为INF
cin>>n>>m;
for(int i=1;i<=m;i++){
int s,e,d;
cin>>s>>e>>d;//一条从 s 到 e 长度为 d 的边
dist[s][e]=min(dist[s][e],d);
}
for(int i=1;i<=n;i++)
dist[i][i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
dist[j][k]=min(dist[j][k],dist[j][i]+dist[i][k]);
int T;
cin>>T;
while(T--){
int i,j;
cin>>i>>j;
cout<<dist[i][j]<<'\n';
}
return 0;
}
单源最短路:求一个起点到其他点的最短路,起点是固定的。
多源最短路:求多个起点到其他点的最短路,起点不固定。
单源最短路算法Dijkstra
点击查看代码
//选一个最短路已经求好的点,选的点一定是当前 dist 最小的点。
//进行松弛操作(用自己的最短路去更新自己周围的最短路)。
#include<bits/stdc++.h>
using namespace std;
vector<pair<int,int>> z[100005];
int dist[100005];//dist[i] 代表从起点到 i 的最短路
bool vis[i];//vis[i]代表 i 的最短路有没有被求出来
void add_edge(int s,int e,int d){
z[s].push_back(make_pair(e,d));
}
int n,m;
void Dijkstra(int s){//以 s 作为起点算最短路
memset(dist,0x3f,sizeof(dist));
dist[s]=0;
for(int i=1;i<=n;i++){
//选一个 dist 最小的点
int p=0;
for(int j=1;j<=n;j++)
if(!vis[j]&&(p==0||dist[j]<dist[p]))p=j;
vis[p]=true;
//用这个点去进行松弛操作
for(int j=0;j<z[p].size();j++){
int q=z[p][j].first;
int d=z[p][j].second;///这是一条从 p 到 q 长度为 d 的边
dist[q]=min(dist[q],dist[p]+d);
}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int s,e,d;
cin>>s>>e>>d;
add_edge(s,e,d);
}
Dijkstra(1);
int T;
cin>>T;
while(T--){
int x;
cin>>x;
cout<<dist[x]<<'\n';
}
return 0;
}