图论杂记
在做图论题时,注意重边,自环,双向边 or 单向边,负环等!!!
记得初始化各种数组!!!
多次计算记得清空该清空的数组和变量!!!
前置芝士:链式前向星,不会的可以看看这篇。
Floyed
适用于较小的数据范围,可求得全源最短路。用邻接矩阵存图,核心思想是一个暴力,枚举两个点,再枚举与它们都有连边的点,不断更新最短路,复杂度 $O(n^{3})$。核心代码:
1 for(int k=1;k<=n;k++){ 2 for(int i=1;i<=n;i++){ 3 for(int j=1;j<=n;j++){ 4 dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]); 5 } 6 } 7 }
Dijkstra
用来求单源最短路径,本质是一种贪心,不适用于负边权,每次确定已知的最小值,再通过这个点去找其它点。
不断确定一直到所有点都被确定。
朴素的 $dij$ 复杂度在 $O(n^{2})$,用堆优化取最小值的过程可达到 $O(mlogn)$。
核心代码:(这里的堆使用 $pair$ 维护)
void dij(int S){ // ans记录最短路条数,如果要求路径记个前驱即可 for(int i=1;i<=n;i++) dis[i]=inf,mrk[i]=0,ans[i]=1; dis[S]=0,ans[S]=1; q.push(MK(0,S)); while(!q.empty()){ int u=q.top().second; q.pop(); if(mrk[u]) continue; mrk[u]=1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to,w=e[i].w; if(dis[v]>dis[u]+w){ dis[v]=dis[u]+w; ans[v]=ans[u]; if(!mrk[v]) q.push(MK(dis[v],v)); } else if(dis[v]==dis[u]+w) ans[v]+=ans[u]; } } }
Bellman_Ford
枚举所有边权直到所有点都不能再被松弛。
核心代码:
建图(稍有不同)
1 struct edge{ 2 int u,v,w;//起点,终点,权值 3 }e[N]; 4 …… 5 for(int i=1;i<=m;i++){//m为边数 6 cin>>u>>v>>w; 7 e[i]=edge{u,v,w};//u与v之间有一条权值为w的边 8 }
松弛
for(int i=1;i<=n-1;i++){//最坏n-1次(一条链) for(int j=1;j<=m;j++){//对m条边进行循环 int u=e[j].u,v=e[j].v; if(dis[v]>dis[u]+e[j].w) dis[v]=dis[u]+e[j].w; } }
Spfa
即队列优化版 Ballman_Ford,本质上也是对点的不断松弛。
核心代码:
1 void spfa(int S){//S为起点 2 // 求最短路条数与dij雷同 3 queue<int>q; 4 for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0; 5 dis[S]=0,vis[S]=1; 6 q.push(S); 7 while(!q.empty()){ 8 int u=q.front(); 9 vis[u]=0; 10 q.pop(); 11 for(int i=head[u];i;i=e[i].nxt){ 12 int v=e[i].to,w=e[i].w; 13 if(dis[v]>dis[u]+w){ 14 dis[v]=dis[u]+w; 15 if(!vis[v]) { 16 q.push(v), 17 vis[v]=1; } 18 } 19 } 20 } 21 return 0; 22 }
判负环:
跑一遍 spfa 或 Bellman_Ford,如果一个点被松弛或入队的次数超过 $n-1$ 则表示有负环。(负环越跑越小)
若程序正常结束则表示没有负环。
1 bool spfa(int S){ 2 queue<int>q; 3 for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0,tot[i]=0; 4 dis[S]=0,vis[S]=1; 5 q.push(S); 6 while(!q.empty()){ 7 int u=q.front(); 8 vis[u]=0; 9 q.pop(); 10 if(tot[u]==n-1) return 1; 11 tot[u]++; 12 for(int i=head[u];i;i=e[i].nxt){ 13 int v=e[i].to,w=e[i].w; 14 if(dis[v]>dis[u]+w){ 15 dis[v]=dis[u]+w; 16 if(!vis[v]) {q.push(v),vis[v]=1;} 17 } 18 } 19 } 20 return 0; 21 }
差分约束
一个不等式组有 $n$ 个未知数,$m$ 个式子形如 $x_i-x_j \leq a$,求这个不等式组的一组解。
显然这组不等式的解不唯一。
我们发现 $x_i-x_j \leq a$ 类似于图中的三角形不等式。
所以我们建图且跑一边最短路,如果有负环则不等式组无解。
为什么呢?
因为对于 i 与 j 之间的最短路满足任意一个点都不能松弛它们,而有负环则永远存在。
所以有解则输出单源最短路径即可。
1 bool spfa(int s){ 2 // 这里建超级源保证图联通,建负边跑最长路与建正边跑最短路雷同 3 queue<int>q; 4 for(int i=0;i<=5005;i++) 5 vis[i]=0,dis[i]=-1,tot[i]=0; 6 vis[s]=1,dis[s]=0,tot[s]=1; 7 q.push(s); 8 while(!q.empty()){ 9 int u=q.front(); 10 vis[u]=0,q.pop(); 11 for(int i=head[u];~i;i=e[i].nxt){ 12 int v=e[i].to,w=e[i].w; 13 if(dis[v]<dis[u]+w){ 14 dis[v]=dis[u]+w; 15 if(!vis[v]){ 16 vis[v]=1; 17 q.push(v); 18 ++tot[v]; 19 if(tot[v]>n+1) return 1; 20 } 21 } 22 } 23 } 24 return 0; 25 } 26 …… 27 add(u,v,-w); 28 if(spfa(0)) printf("NO\n"); 29 else for(int i=1;i<=n;i++) printf("%d ",dis[i]);
Ballman_Ford 做法雷同。
最小环
$Floyed$ 求解。
代码是无向图的,有向图存一次就好了。
1 for(int i=1;i<=n;i++) 2 for(int j=1;j<=n;j++) 3 if(i!=j)dis[i][j]=mp[i][j]=inf; 4 for(int i=1;i<=m;i++){ 5 scanf("%lld %lld %lld",&u,&v,&w); 6 dis[u][v]=min(dis[u][v],w); 7 dis[v][u]=min(dis[v][u],w);//处理重边 8 mp[u][v]=min(mp[u][v],w); 9 mp[v][u]=min(mp[v][u],w); 10 } 11 for(int k=1;k<=n;k++){ 12 for(int i=1;i<k;i++){ 13 for(int j=i+1;j<k;j++) 14 ans = min(ans,dis[i][j]+mp[i][k]+mp[k][j]); 15 } 16 for(int i=1;i<=n;i++) 17 for(int j=1;j<=n;j++){ 18 dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]); 19 dis[j][i] = dis[i][j]; 20 } 21 }
LCA
1 inline int lCa(int u,int v){ 2 if(dp[u]<dp[v]) swap(u,v); 3 int dc=dp[u]-dp[v]; 4 for(int i=22;i>=0;i--){ 5 int b=1<<i; 6 if(b<=dc){ 7 dc-=b; 8 u=fa[u][i]; 9 } 10 } 11 if(u==v) return u; 12 for(int i=22;i>=0;i--){ 13 if(fa[u][i]==fa[v][i]) continue; 14 u=fa[u][i]; 15 v=fa[v][i]; 16 } 17 return fa[u][0]; 18 }
先跑一遍dfs处理出深度,再倍增跳父亲节点直到find ans
复杂度 $O(nlogn)$。
欧拉路
有向图欧拉路径:图中恰好存在 1 个点出度比入度多 1(这个点即为 起点 S),1个点入度比出度多 1(这个点即为 终点 T),其余节点出度=入度。
有向图欧拉回路:所有点的入度=出度(起点 S 和终点 T 可以为任意点)。
无向图欧拉路径:图中恰好存在 2 个点的度数是奇数,其余节点的度数为偶数,这两个度数为奇数的点即为欧拉路径的 起点 S 和 终点 T。
无向图欧拉回路:所有点的度数都是偶数(起点 S 和终点 T 可以为任意点)。
注:存在欧拉回路(即满足存在欧拉回路的条件),也一定存在欧拉路径。
当然,一副图有欧拉路径,还必须满足将它的有向边视为无向边后它是连通的(不考虑度为 0 的孤立点),连通性的判断我们可以使用并查集或 dfs 等。
最后倒序输出栈内节点即可
以有向图欧拉路径为例,其他类似
1 void pushin(int x){ 2 top++; 3 sta[top]=x; 4 } 5 void popout(){ 6 sta[top]=0; 7 top--; 8 } 9 10 void dfs(int node){ 11 for(int i=vis[node];i<e[node].size();i=vis[node]){ 12 vis[node]=i+1; 13 dfs(e[node][i]); 14 } 15 pushin(node); 16 } 17 18 int main(){ 19 scanf("%d%d",&n,&m); 20 int S=1,cnt0=0,cnt1=0; 21 for(int i=1;i<=m;i++){ 22 scanf("%d%d",&u,&v); 23 e[u].push_back(v); 24 chudu[u]++; 25 rudu[v]++; 26 } 27 for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end()); 28 bool flag=1; 29 for(int i=1;i<=n;i++){ 30 if(chudu[i]!=rudu[i]){ 31 flag=0; 32 if(chudu[i]-rudu[i]==1){ 33 S=i; 34 cnt0++; 35 } 36 else if(rudu[i]-chudu[i]==1) cnt1++; 37 else{ 38 printf("No\n"); 39 return 0; 40 } 41 } 42 } 43 if(!flag&&!(cnt1==cnt0&&cnt1==1)){ 44 printf("No\n"); 45 return 0; 46 } 47 dfs(S); 48 while(top!=0){ 49 printf("%d ",sta[top]); 50 popout(); 51 }
topo排序
最小生成树
对所有的边排序,然后用并查集维护连通块,贪心地加边即可
1 vector<pair<int,int>> e; 2 //e记答案,n为点数,m为边数 3 void kruskal(){ 4 int cnt=0; 5 for(int i=1;i<=m;i++){ 6 if(findfa(b[i].u)==findfa(b[i].v)) continue; 7 con(b[i].i,b[i].v); 8 cnt++; 9 e.push_back(make_pair(b[i].u,b[i].v)); 10 if(cnt==n-1) break; 11 } 12 }
Thanks for reading !

浙公网安备 33010602011771号