图论模板
链式前向星
cpp
const int N=2e5+7;
int h[N],e[N],ne[N],w[N];
int idx=0;
void init(){
memset(h,-1,sizeof h);
}
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
Hamilton型问题和Eular型问题
在蓝桥杯中,我们经常可以看到Hamilton限制(每个点只能走一次)和Eular限制(每条边只能走一次)的问题,在16届蓝桥杯的压轴题中给出的正是Eular型限制,对于具体的问题而言,正解的方式会有所不同,但是我们可以总结出利用暴力搜索来解决该类问题的模板。
也就是(Eular型遍历:每条边只走一次)(Hamilton型遍历,每个点只走一次)
Eular型遍历
存储:
cpp
int h[N],e[N],ne[N],idx;//存储图
int vt[N];// 用来记录边的访问情况
int pr[N];//用来记忆每条边的反向边是哪条边
函数:
cpp
int h[N],e[N],ne[N],idx;//存储图
int vt[N];// 用来记录边的访问情况
int pr[N];//用来记忆每条边的反向边是哪条边
void init(){}//初始化h
int add(int a,int b)//添加边返回边的编号
void dfs(int x,int edge)//执行dfs操作,参数表示通过边edge到达的点x
模板:
cpp
void init(){
memset(h,-1,sizeof h);
}
int add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
return idx-1;
}
void dfs(int x,int edge){//表示现在通过边edge到达了点x
vt[edge]=true;
vt[pr[edge]]=true;
if (满足题目某种条件){
//执行题目中的要求记忆方案数记忆最大值...
}
for (int i=h[x];~i;i=ne[i]){
int j=e[i];
if (vt[i])
continue;
dfs(j,i);
}
vt[edge]=false;
vt[pr[edge]]=false;
}
int main(){
//加边操作时这样修改
int a,b;
scanf("%d%d",&a,&b);
int x=add(a,b);
int y=add(b,a);
pr[x]=y;
pr[y]=x;
}
Hamilton型遍历
本文暂且不表
树相关问题
求depth和p
利用链式前向星对树进行存储后,我们没有树的深度信息和父节点信息。我们可以选定一个根节点,通过dfs求解出depth(每个节点的深度)和p(每个节点的父母)
存储:
int h[N],e[N],ne[N],idx;
int depth[N],p[N];
函数:
void dfs(int u,int p){
//直接用一遍dfs结束depth和p的运算
}
模板:
cpp
int h[N],e[N],ne[N],idx=0;
int depth[N],p[N];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
add(a,b);
add(b,a);
void dfs(int x,int f){
p[x]=f;
for (int i=h[x];i!=-1;i=ne[i]){
int j=e[i];
if (j==f){
continue;
}
depth[j]=depth[x]+1;
dfs(j,x);
}
}
倍增求LCA
这里我们介绍求LCA的倍增算法
倍增算法求LCA就是说利用一个记忆数组f[i][j]来记录
对于每个节点i而言,其向上2^j个祖先是谁
倍增LCA的实现需要在刚才使用dfs求出每个节点深度的同时
递推求出f数组的值
具体操作见下述dfs
cpp
void dfs(int now,int parent){
deepth[now]=deepth[parent]+1;
f[now][0]=parent;
for (int i=1;i<=lg[deepth[now]]-1;i++){
f[now][i]=f[f[now][i-1]][i-1];
}
for (int i=h[now];~i;i=ne[i]){
if (e[i]!=parent)
dfs(e[i],now);
}
}
利用刚才预处理得到的f数组我们就可以直接O(logn)进行查询
cpp
// 预处理 (你的笔记版本)
lg[1] = 0;
for (int i = 2; i <= n; i++) lg[i] = lg[i / 2] + 1;
// LCA 里的跳跃逻辑
int LCA(int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
// 跳到同一高度
while (depth[x] > depth[y]) {
// 因为你的 lg[i] 已经是指数了,直接用即可
x = f[x][lg[depth[x] - depth[y]]];
}
if (x == y) return x;
// 一起向上跳 (注意这里建议依然用从大到小的 for 循环)
for (int i = lg[depth[x]]; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
最短路算法
Floyd算法
Floyd算法是在邻接矩阵上运行的最短路算法,需要手动取最小值来处理重边问题,可以解决负边权的问题,但是不能解决有负环的问题。时间复杂度为O(n^3)
cpp
//Floyd
/*代码实现中需要注意一下两点:
1.矩阵初始化为inf,g[i][i]初始化为0
2.松弛操作的k需要是三重循环中的第一重
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#include<map>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
int n,m;
const int N=105;
int g[N][N];
int main(){
memset(g,0x3f3f3f3f,sizeof g);
cin>>n>>m;
for (int i=1;i<=n;i++){
g[i][i]=0;
}
while (m--){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g[u][v]=g[v][u]=min(g[u][v],w);
}
for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
cout<<g[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
cpp
//Dijkstra算法(堆优化Ologn)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef pair<int ,int > PII;
const int N=100010;
const int M=200010;
const int INF=0x3f3f3f3f;
int n,m,s;
bool vis[N];
int dist[N];
int h[M],e[M],w[M],ne[M],idx=1;
priority_queue<PII ,vector<PII> ,greater<PII> > heap;
void add(int a,int b,int c){
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
void dijkstra(){
memset(dist,INF,sizeof (dist));
dist[s]=0;
heap.push({0,s});
while (!heap.empty()){
PII t=heap.top();
heap.pop();//这句不能忘记写
int ver=t.second;
if (vis[ver]) continue;
vis[ver]=true;
int distance=t.first;
for (int i=h[ver];i;i=ne[i]){ //遍历图的时候要记住变量是h[ver]
int j=e[i];
if (dist[j]>w[i]+distance){ //注意这里写的是w[i]+distance
dist[j]=distance+w[i];
if (!vis[j])
heap.push({dist[j],j});
}
}
}
}
int main(){
cin>>n>>m>>s;
for (int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
dijkstra();
for (int i=1;i<=n;i++)
cout<<dist[i]<<' ';
return 0;
}
cpp
//SPFA算法(BellmanFold队列优化版本)
# include <bits/stdc++.h>
using namespace std;
int n , m , s , u , v , w , dis[10010];
bool vis[10010];
vector <pair <int , int> > g[10010];
void bfs()
{
for(int i = 1 ; i <= n ; i ++)
{
dis[i] = 1e9;
}
queue <int> q;
q.push(s);
vis[s] = 1;
dis[s] = 0;
while(!q.empty())
{
int t = q.front();
q.pop();
vis[t] = 0;
for(int i = 0 ; i < g[t].size() ; i ++)
{
if(dis[g[t][i].first] > dis[t] + g[t][i].second)
{
dis[g[t][i].first] = dis[t] + g[t][i].second;
if(vis[g[t][i].first])
{
continue;
}
vis[g[t][i].first] = 1;
q.push(g[t][i].first);
}
}
}
return ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> s;
for(int i = 1 ; i <= m ; i ++)
{
cin >> u >> v >> w;
g[u].push_back({v , w});
}
bfs();
for(int i = 1 ; i <= n ; i ++)
{
if(dis[i] == 1e9)
{
cout << (1LL << 31) - 1 << " ";
}
else
{
cout << dis[i] << " ";
}
}
return 0;
}
最小生成树算法(MST)
Kruskal算法
cpp
//算法流程:
/*
1.将所有边排序
2.从小到大遍历所有边,如果这个边与之前选择的边不构成圈,则选择这个点
3.否则,continue
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
const int N=4e5+7;
int n,m;
struct edge{
int a;
int b;
int c;
bool friend operator < (edge a,edge b){
return a.c<b.c;
}
};
int h[N];
edge e[N];
int p[N];
int r[N];
int find(int x){
if (x==p[x])
return x;
return p[x]=find(p[x]);
}
void merge(int a,int b){
int roota=find(a);
int rootb=find(b);
if (roota==rootb)
return ;
if (r[roota]<r[rootb]){
p[roota]=rootb;
r[rootb]+=r[roota];
}
else {
p[rootb]=roota;
r[roota]+=r[rootb];
}
}
void init(){
memset(h,-1,sizeof h);
for (int i=1;i<=n;i++){
p[i]=i;
r[i]=1;
}
}
int main(){
cin>>n>>m;
init();
for (int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
e[i].a=a;
e[i].b=b;
e[i].c=c;
}
sort(e,e+m);
int cnt=0;
int sum=0;
for (int i=1;i<=m;i++){
int roota=find(e[i].a);
int rootb=find(e[i].b);
if (roota==rootb)
continue;
merge(e[i].a,e[i].b);
cnt++;
sum+=e[i].c;
if (cnt==n-1){
cout<<sum<<endl;
return 0;
}
}
if (cnt<n-1)
cout<<"orz"<<endl;
return 0;
}
对于最大瓶颈问题,我们只需要跑一遍最小生成树,在跑的同时记录树中边权最小的边即可。