目录
图论
最小生成树
\(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重构树
最短路
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
推荐洛谷日报
Tarjan算法运行过程
1.按照深度优先遍历的方式遍历这张图。
2.遍历当前节点所出的所有边。在遍历过程中:
( 1 ) 如果当前边的终点还没有访问过,访问。回溯回来之后比较当前节点的low值和终点的low值。将较小的变为当前节点的low值。(因为遍历到终点时有可能触发了2) ( 2 ) 如果已经访问过,那我们一定走到了一个之前已经走过的点(终点的时间戳一定比当前的小)
3.则比较当前节点的low值和终点的dfn值。将较小的变为当前节点的low值
4,在回溯过程中,对于任意节点u用其出边的终点v的low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。
当一个节点的dfn值和low值相等时,这个节点是一个强联通分量的“根”。压栈,输出。
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; }
来源:https://www.cnblogs.com/pyyyyyy/p/11329739.html