图论杂记

在做图论题时,注意重边,自环,双向边 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 !

 

posted @ 2022-07-26 14:59  々Trouvaille々  阅读(37)  评论(0)    收藏  举报