图论笔记

和自甴很熟 提交于 2019-11-26 17:52:17

图论

最小生成树

$N$个城市,$M$条可修的公路,每条公路有一个修的成本$w_i$,要使$N$个城市连通,所需要的最低成本?

最少需要$N-1$条边,构成一棵树。

## Kruskal算法证明

对图的顶点数$n$做归纳,证明$Kruskal$算法对任意$n$阶图都适用

归纳基础

$n=1$,显然能找到最小生成树

归纳过程

例题

P1550 [USACO08OCT]打井Watering Hole

思路就是建立一个超级源点往每个点建一条边权为$w_i$的边,然后跑最小生成树

怎么证明合数都可以分成素数的乘积

就不写了,我太菜了

在最大生成树上求两点路径中边权的最小值

int f[N][20]; //i的第2^j祖先 int mn[N][20]; //i往上跳2^j祖先所经过的边的最小值  int query(int u,int v) {     int ans=inf;     if(dep[u]>dep[v]) swap(u,v);     for(int i=0,k=dep[v]-dep[u];i<=LG[k];++i)         if(k>>i&1) {             ans=min(ans,mn[v][i]);             v=f[v][i];         }     if(u==v) return ans;     for(int i=LG[dep[u]];i>=0;--i)         if(f[u][i]!=f[v][i]) {             ans=min(ans,min(mn[u][i],mn[v][i]));             u=f[u][i],v=f[v][i];         }     ans=min(ans,min(mn[u][0],mn[v][0]));     return ans; } 

kruskal重构树

orz lgj 学长,竟然是学长的博客

最短路

SPFA判负环

queue<int> q; bool inq[N]; int dis[N], len[N]; bool spfa() { //返回值为true表示有负环 否则没有负环      fill(dis+1,dis+n+1,inf);     dis[1]=0,len[1]=1;     q.push(1);     inq[1]=true;     while(!q.empty()) {         int u=q.front() ; q.pop();         inq[u]=false;         for(int i=0;i<e[u].size();++i) {             int v=e[u][i];             int w=W[u][i];             if(dis[u]+w<dis[v]) {                 dis[v]=dis[u]+w;                 len[v]=len[u]+1;                 if(len[v]>=n) return true;                 if(!inq[v]) q.push(v),inq[v]=true;             }         }     }     return false; } 

建图技巧

通过添加虚拟点等手段将问题转化

从而达到减少边数等目的。

骚操作,长见识了

拓扑排序

在一个$DAG$有向无环图) 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 $u$ 到 $v$ 的有向边 $(u,v)$ , 都可以有 $u$ 在 $v$ 的前面。

//拓扑排序 O(n^2) int ind[N]; //每个点的入度 int seq[N],cnt; //求出的拓扑序 bool vis[N]; bool toposort() { //有环返回false      while(true) {         if(cnt==n) return true;         int k=0;         for(int i=1;i<=n;++i)             if(!vis[i]&&ind[i]==0) {                 k=i; break;             }         if(k==0) return false;         seq[++cnt]=k; vis[k]=true;         for(int i=0;i<e[k].size();++i) {             int u=e[k][i];             --ind[u];         }     } }

queue优化

$O(n+m)$

//拓扑排序 O(n+m) queue<int> q; //q中维护入度为0的点  int ind[N],seq[N],cnt; bool toposort() { //有环返回false     for(int i=1;i<=n;++i)         if(!ind[i]) q.push(i);     while(!q.empty()) {         int u=q.front(); q.pop();         seq[++cnt]=u;         for(int i=0;i<e[u].size();++i) {             int v=e[u][i];             --ind[v];             if(ind[v]==0) q.push(v);         }     }     return cnt==n; }

tarjian

int dfn[N],low[N],dfsclock; int s[N],top; int cnt; //当前联通块的编号 int bl[N]; //bl[i]表示i所在的强联通分量编号  vector<int> scc[N]; //scc[i]中存储编号为i强联通分量中的所有点 void dfs(int u) {     low[u]=dfn[u]=++dfsclock;     s[++top]=u;     for(int i=0;i<e[u].size();++i) {         int v=e[u][i];         if(dfn[v]) low[u]=min(low[u],dfn[v]);         else {             dfs(v);             low[u]=min(low[u],low[v]);         }     }     if(low[u]==dfn[u]) {         ++cnt;         int v=s[top];         while(v!=u) {             bl[v]=cnt;             scc[cnt].push_back(v);             v=s[--top];         }         bl[u]=cnt;         scc[cnt].push_back(u);     } }

## 缩点

一个强连通分量里的点如果看成是一个大点的话,图就可以变成有向无环图($DAG$),然后递推什么的就方便了

差分约束

以后再写吧,感觉就知道是吧不等式组建了个图...我太弱了

环套树/基环树

我们知道,树是最简单的连通无向图。

树中任意两点之间有且只有一条路径

环套树/基环树:树中加一条边的图。

设加的一条边为$(u,v)$,那么由之前$u,v$间有一条路径,加了这条边之后这条路径和这条边构成一个环。

这也是形成的唯一一个环

我们可以把环放在图中心,这样看起来环上每个点都是一个树根。
找到环的位置很重要

方法类似$noip2016 信息传递$

基环树找环

找环的步骤:

1.随意找一个点开始当成树进行$dfs$,并记录每个结点访问的时间戳$dfn$

2.$dfs$的过程中一定会有一个点往$dfn$比自己小的点连了边,那么这条边可以看成加上的那条。记录下这条边$(u,v)$

3.暴力让$u$和$v$往上爬到根,记录他们分别经过的点。

4.深度最大的他们都经过的点为他们的$lca$,$u->lca$之间的所有点$+v->lca$之间的所有点即构成环。

//求基环树中的环 方法一 int fa[N]; //f[i]为i在搜索树中的父亲结点  bool in[N],vis[N]; //表示i是否在环中 int cir[N],cnt; int x,y; void dfs(int u,int f) { //f为u的父亲结点      fa[u]=f; vis[u]=1;     for(int i=0 ;i<e[u].size();++i) {         int v=e[u][i];         if(v==f) continue;         if(vis[v]) x=u,y=v;         else dfs(v,u);     } } void solve() { //x到y一定是返祖边      dfs(1,0);     while(x!=y) {         cir[++cnt]=x;         x=fa[x];     }     cir[++cnt]=y; }
//方法二 queue<int>q; int deg[N],n; void solve() {     for(int i=1;i<=n;++i) in[i]=1;     for(int i=1;i<=n;++i)         if(deg[i]==1) q.push(i);     while(!q.empty())     {         int u=q.front();         q.pop();         int[u]=0;         for(int i=0;i<e[u].size();++i)         {             int v=e[u][i];             deg[v]--;             if(deg[v]==0) q.push(v);         }     }     int u;//u作为环的起点     for(u=1;u<=n;++u)     if(in[u]) break;     int v=u,las=0;//把u放环里     do{         cir[++cnt]=v;         int k;         for(i=0;i<e[v].size();++i)         {             k=e[v][i];             if(in[k] && k!=las) break;         }         la         v=k;     }while(v!=u) }

欧拉图

通过图中所有边一次且仅一次行遍所有顶点的通路称为欧拉通路。

通过图中所有边一次且仅一次行遍所有顶点的回路称为欧拉回路。

具有欧拉回路的图称为欧拉图。

具有欧拉通路的图称为半欧拉图。

有向图的时候可以类似地定义。

$G$是欧拉图当且仅当$G$是连通的且没有奇度顶点。

$ G$是半欧拉图当且仅当$G$中恰有两个奇度顶点。

证:

连通且没有奇度顶点=>一定存在一个环

那么我们把这个环上的边删掉,剩下的图仍满足连通且没有奇数顶点

由连通性这些环可以是有公共点的,可以拼起来

圈套圈算法

$dfs$搜索,不能再往下走(不能重复使用一条边,但可以重复经过一个点)便回溯,回溯时记录路径,回溯时不清除对边的标记,最后求出来的路径就是欧拉回路。

给边编号,用$vis$数组记录每个边是否访问过

//欧拉回路 圈套圈算法 struct edge {     int v,nex,id; }; bool vis[N]; int s[N],top; int seq[N],cnt; void dfs(int u) {     s[++top]=u;     for(int i=head[u];i;i=e[i].nex)     {         if(vis[e[i].id]) continue;         vis[e[i].id]=true;         dfs(e[i].v);     }     --top;     seq[++top]=e[i].v; }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!