网络流另开了一个专题,所以在这里就不详细叙述了。


一般表示为\(G=(V,E)\),V表示点集,E表示边集

定义图G为简单图,当且仅当图G没有重边和自环。

对于图G=(V,E)和图G2=(V2,E2) ,若V2是V的子集,E2是E的子集,那么图G2是图G的子图

拓扑排序问题

对于有向图,如果不存在环,则称为有向无环图,缩写为DAG。

拓扑排序是对DAG找出一个点的序列,使得如果x->y,那么x在序列中在y的前面。

拓扑排序是非常简单的,我们初始将所有入度为0的点丢入队列中,然后进行bfs。

每次取出队头x,对于所有x->y,将y的入度减一,如果此时y入度为0则将y加入队尾。最后队列中就是合法的拓扑序列。

常见的应用还有,如果对一个有向图做拓扑排序无法访问到所有点,可以证明这个有向图是有环的。

欧拉回路

入度必须都是偶数,如果有奇数的,一定不是欧拉回路。

如何输出方案?直接按照度数递归下去即可,注意输出方案一定要在回溯之后。

inline void search(int x)
{
for(int i=1;i<=50;i++)
if(done[x][i])
{
done[x][i]--,done[i][x]--;
search(i);
printf("%d %d\n",i,x);
}
}

最短路

floyd 时间复杂度\((n^3)\)

从大到小枚举中间点k,然后枚举任意两个点i,j,用dist[i][k]+dist[k][j]去更新dist[i][j]。

dij 时间复杂度\((n^2+m)\)

适用性:没有负边权的单元最短路问题

过程:设起点为s,令\(f[x]\)表示从s到x的最短路,显然初始f[s]=0,其余f[x]=inf。初始所有点均为白点,每次选择f最小的一个白点作为当前点。枚举当前点的出边,用当前点更新其余的d,将当前点染黑。重复上述过程直至所有点都是黑点。

正确性证明:因为所有的边权均为非负整数,那么对于s->x最短路的一个方案,若最后一条边是y->x,显然这个方案由s->y加上y->x组成,即先走到y的最短路,再从y走到x。

因此我们用黑点表示最短路已经确定了的点,到最后所有的点都是黑点的时候算法结束。

dij+堆优化 时间复杂度\(O((n+m)logn)\)


SPFA算法(她死了)

对于有负权,而没有负环的图,最短路仍然是有意义的,但是dij无法处理(大家想一下它的工作原理)

SPFA算法就是用一个队列来维护当前点,每次将队头当作当前点进行更新,并将被更新而不在队列中的点加入队尾,直至队列为空。

时间复杂度为\(O(nm)\)

数据结构优化建图

有些图的边数过于巨大,往往让我们很难直接在上面应用最短路算法。所以我们就可以用数据结构优化建图。

原理是保证图的等价性,而缩小边数。比如说点x向所有编号在区间[l,r]内的点都连相同边权的有向边,就可以对这些点建立线段树,每个节点向两个儿子连边权为0的有向边,每个叶子向对应原节点也这样连。那么对于x连向[l,r],显然可以变成x连向logn个线段树节点。


差分约束

其实就是利用最短路那个不等式,然后建立模型即可。


最小生成树

切割性质

将点集v分成s和v-s,一端在s内另一端在v-s内边权最小的边,一定出现在最小生成树中。(这里默认所有边权都不相同)

Prim算法

初始选定一个点为黑点,其余点为白点,每次找到一条连接黑点和白点的边权最小的边,将该边加入到生成树中,并将白点变为黑点。重复上述过程直到所有点都是黑点,最小生成树即求出。 堆优化之后可以做到\(O((n+m)logm)\)

Kruskal算法

一开始每个点自成一个联通块。将边按照边权从小到大排序,依次扫描,如果一条边的两端点不在一个联通块,加入并合并联通块。最终形成的就是最小生成树。复杂度瓶颈是将边排序。、

Boruvka算法

一开始每个点自成一个联通块,每次所有联通块都找一条边权最小的边,其中一端在该联通块而另一端不在,接下来加入这些边并合并联通块。(如果这些边成环则最大边权的边不该加入)

只剩一个联通块时算法结束。

正确性有保证是因为利用了切割性质。

算法复杂度与每一轮的执行时间有关,最多执行logn轮。


prufer序列

对于一个n个节点的带标号无根树,定义其prufer序是一个长度为n-2,值域为[1,r]的序列。

对于一个确定的n个节点的带标号无根树,得到其prufer序列的方法:

1、将树中标号最小的叶子删除,并将其连接的点的标号加入当前序列的末尾。

2、重复上述过程直到剩下两个节点。

一颗带标号无根树对于一个prufer序,一个prufer也对应一个带标号无根树,也就是说这是一个双射。

可以由上面得出:n个节点的不同带标号无根树的个数为\(n^{n-2}\)


无向图的一些定义

割点

若删掉某点P(及与其相连的边)后,原图G分裂成两个或以上的子图。那么P为原图的一个割点

割边(桥)

若删掉某边E后,原图G分裂成两个或以上的子图。那么E是原图的一个割边

点双连通图

如果一个图上没有割点,那么该图为点双连通图。在一个点双连通图里,任意两点间存在至少两条不经过重复点的路径

对于一个连通图,如果任意两点之间至少存在两条“点不重复”的路径,就说这个图是点双联通的(一般称作双联通)。这个要求等价于任意两条边都在同一个简单环中。

inline int tarjan(int x,int fa)
{
int lowx=dfn[x]=++tot;
int child=0;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
Line e=(Line){x,v};
if(dfn[v]==0)
{
s.push(e);
child++;
int lowv=tarjan(v,x);
lowx=min(lowx,lowv);
if(lowv>=lowx)
{
bcc[++cnt].clear();
iscut[x]=1;
for(;;)
{
Line cur=s.top();s.pop();
if(id[cur.u]!=cnt) id[cur.u]=cnt,bcc[cnt].push_back(cur.u);
if(id[cur.v]!=cnt) id[cur.v]=cnt,bcc[cnt].push_back(cur.v);
if(cur.u==x&&cur.v==v) break;
}
}
}
else if(dfn[v]<dfn[x]&&v!=fa)
{
lowx=min(lowx,dfn[v]);
s.push(e);
}
}
if(child==1&&fa<0) iscut[x]=0;
return lowx;
}

边双联通图

如果一个图上没有割边,那么该图为边双连通图。

在一个边双连通图里,任意两点间存在至少两条不经过重复边的路径

G的某个极大点/边双联通子图为G的点/双联通分量

一个有桥的连通图,如何把它通过加边变成边双连通图?

首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。

统计出树中度为1的节点的个数,即为叶节点的个数,记为ans。则至少在树上添加(ans+1)/2条边,就能使树达到边二连通,所以至少添加的边数就 是(ans+1)/2。

首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为 一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(ans+1)/2次,把所有点收缩到了一起。


无向图的tarjan

这个算法可以用以找出无向图中所有点/边双联通分量。而其主要思路是遍历dfs树求出dfn和low。

(实现时无需先求出dfs树才做Tarjan算法,可以合并)

对于点i定义:

dfn[i]为dfs时i的时间戳,即i是第几个被dfs到的节点。

low[i]为从i出发,每个点只能通过树边往子树走,或通过非树边走到祖先,能够走到的dfn最小值。

具体实现:枚举从i出发的边(i,j),如果dfs树上j是i的儿子,那么low[i]=min(low[i],low[j]),否则low[i]=min(low[i],dfn[j])(此处需满足该边为返祖边,即没有枚举到dfs树上连接i与其父亲的边)。


无向图上求割点

对于一个点i,有以下情况:

i是dfs树的根,如果i有两个或以上的儿子,那么i是割点

i不是树根,如果存在i的一个儿子j,满足low[j]≥dfn[i],那么i是割点(此时j不能通过返祖边与i的祖先联通)

//核心代码
void tarjan(int u,int f)
{
dfn[u]=low[u]=++cnt;
int child=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v,f);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=f)
cut[u]=1;
if(u==f)
child++;
}
low[u]=min(low[u],dfn[v]);
}
if(child>=2&&u==f)
cut[u]=1;
}

无向图求点双

在Tarjan算法过程中,用栈存dfs过的点。

即每dfs一个新的点将其加入栈中。

其含义是我们用栈保存了按照dfn排序的目前还能进入新的点双里的非割点。

对于点u,如果它的儿子v满足low[v]≥dfn[u],那么u是割点,这时应不断退栈直到v被弹出,那么弹出的所有点和u形成一个点双。

一个割点可以存在于多个点双中(比如说:菊花图)

性质:对于大于2个点组成的点双,任意两点间存在两条除起点和终点外点不相交的路径。(若不存在,相交部分显然为割点,则不符合点双定义)


无向图求割边

首先非树边不可能是割边。

对于一条树边(i,j),其中父亲是i,儿子是j,那么如果low[j]>low[i],那么该树边是割边。

这可以表述为:如果low[i]=dfn[i],那么i与其父亲连接的边为割边。


无向图求边双

在Tarjan算法过程中,用栈存dfs过的点。

在对u的子树dfs完后,如果dfn[u]=low[u],那么u与其父亲连接的树边为割边。此时应不断弹栈直到弹出u,此时弹出的所有点形成一个边双。

割边不存在于任何一个边双里,其它边都恰好存在于一个边双里


强连通分量

在有向图G中,若两个顶点u,v之间能够相互到达,则称u,v是强连通的,若有向图G的每两个顶点都强连通,则G是一个强连通图。

有向图的极大强连通子图,被称为强连通分量。

首先强连通分量是针对于有向图的+有向图G中每一个点都属于且仅属于一个强连通分量+具有传递性。(即AB一个,BC一个的话,AC也在同一个)】

和上面针对于无向图提出的算法过程差不多。具体可以看下下面的缩点代码qwqwq

缩点

缩点之后的图一定是一个有向无环图。就是求出强连通分量之后给在同一个强连通分量的点重新染色。之后再把不同颜色的点重新连起来就完成了缩点。

//核心代码
void tarjan(int p)
{
s[++tot]=p;
ex[p]=1;
dfn[p]=low[p]=++cnt;
for(int i=head[p];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v);
low[p]=min(low[p],low[v]);
}
else if(ex[v])
low[p]=min(low[p],dfn[v]);
}
if(dfn[p]==low[p])
{
num++;
while(s[tot+1]!=p)
{
id[s[tot]]=num;
sum[num]+=val[s[tot]];
ex[s[tot--]]=0;
}
}
}

圆方树

圆方树是对于连通无向图定义的一颗树,其中每个原图中的点都叫圆点,而我们为每个点双新建一个方点,而圆方树上所有边都是由一个方点和一个属于其对应点双的圆点相连这种形式。

性质

  • 只有圆点连向方点的边,任意圆点和方点之间都没有边。
  • 对于给定点对,其在圆方树路径上的所有圆点都是必经点。

在图中找环

有向图

有向图判断是否有负环——直接spfa即可,如果一个点入队次数超过n,就确定它存在负环。

有向基环树找环——直接上dfs即可。、

有向基环树找环代码:

inline void search(int x,int pre)
{
if(flag==false) return;
fa[x]=pre;
done[x]=1;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(done[v])
{
int now=x;
vec.push_back(v);
while(now!=v)
{
vec.push_back(now);
now=fa[now]; }
flag=false;
return;
}
else search(v,x);
}
}

有向图找环——和有向基环树差不多,就是每次找到一个环之后要把它锁起来,然后再找。时间复杂度上不太优秀。。。

无向图

无向基环树找环——直接拓扑排序,到最后度数>=2的就是环上的点。

无向图找环代码:

拓扑排序

inline void toposort()
{
queue<int>q;
for(int i=1;i<=n;i++)
if(in[i]==1)
q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
in[v]--;
if(in[v]==1) q.push(v);
}
}
for(int i=1;i<=n;i++)
if(in[i]>=2)
v.push_back(i);//v里面的就是环上的点
}

tarjan(其实也就是求出来割边即可,注意割边和割点的不同)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<vector>
#define MAXN 100010
using namespace std;
int n,m,t=1,cnt,tot;
int head[MAXN<<1],dfn[MAXN],low[MAXN],cut[MAXN],color[MAXN];
vector<int>vec[MAXN];
struct Edge{int nxt,to;}edge[MAXN<<1];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void tarjan(int x,int f)
{
dfn[x]=low[x]=++cnt;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v,i);
low[x]=min(low[x],low[v]);
if(low[v]>dfn[x]) cut[i]=cut[i^1]=1;
}
else if(i!=(f^1))
low[x]=min(low[x],dfn[v]);
}
}
inline void search(int x)
{
color[x]=tot;
vec[tot].push_back(x);
for(int i=head[x];i;i=edge[i].nxt)
{
if(cut[i]) continue;
int v=edge[i].to;
if(color[v]) return;
search(v);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i,0);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].nxt)
if(cut[j]&&edge[j].to>i)
printf("cut %d->%d\n",i,edge[j].to);
for(int i=1;i<=n;i++) ++tot,search(i);
for(int i=1;i<=tot;i++)
{
for(int j=0;j<vec[i].size();j++)
printf("%d ",vec[i][j]);
puts("");
}
return 0;
}

三元环计数

给定一个n个点m条边的无向图,问有多少个三元组(u,v,w)满足两两之间有边相连。

我们先把无向图转成有向图,并给每个点定义一个双关键字\((deg_i,id_i)\),其中\(deg\)表示度数,\(id\)表示标号,这样对于每一对点都能严格比较出大小。

我们把每一条边重定向成从度数大的点连向度数小的点,我们就可以得到一张有向无环图。

枚举一个点i,将所有i点连出的点标记为i。

枚举一个i连出的点j。

枚举一个j连出的点k,如果k的标记是i,那么就找到了一组三元环\((i,j,k)\)。

分析每一个三元环只会在\(i\)这个点被算到一次答案。

四元环计数

先和三元环一样,把每个点排出来rank。

然后枚举两条边,找每个点距离2的点x,将x的标记加入答案,然后往上面标记+1


SAT问题

2-SAT问题

时间复杂度一般为\(O(n+m)\),如果是要求字典序最小,就更暴力了,是\(O(nm)\)

一般就是如果存在“选A必须选B”,那么从A向B连一条边。

2-SAT的构图具有对称性。

如果最后一遍tarjan下来同一个东西的两种决策(选或者不选)在一个强连通分量里,那么就是不存在一个合法解。

如果存在合法解的话,直接构造出来就行了。

3-SAT问题

这是NPC问题qwq


竞赛图与哈密顿回路

竞赛图:指任意两个顶点间恰有一条有向边的有向图

哈密顿回路:指除起点和终点外经过所有顶点恰好一次且起点和终点相同的路径

n个点的竞赛图中哈密顿回路的总数为\((n-1)!2^{\frac{(n-1)n}{2}-n}\)

因为我们形成一个哈密顿回路的话是一个排列,一共有\(n!\)种情况,但又因为是一个圆排列,所以要除以n

然后连边的话是有\(n(n-1)/2\)种,去掉形成环的n个,就是\(n(n-1)/2-n\)种,然后因为有向,所以是2的次方。

OI图论 简单学习笔记的更多相关文章

  1. OI数学 简单学习笔记

    基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. PART1 数论部分 最大公约数 对于正整数x,y,最大的能同时整除它们的数称为最大公约数 常用的:\(lcm(x,y)=xy\ ...

  2. OI网络流 简单学习笔记

    持续更新! 基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. ..怎么说呢,最基础的模板我就我不说了吧qwq,具体可以参考一下这位大佬写的博客:最大流,最小割,费用流 费用流 跑 ...

  3. OI计算几何 简单学习笔记

    学习平面几何,首先我们要会熟练地应用向量,其次也要知道一些基本的几何知识.(其实看看数学课本就可以了吧) 因为是看的蓝书,所以很多东西做了引用.(update:还参考了赵和旭dalao的讲义) 下面先 ...

  4. OI多项式 简单学习笔记

    咕咕咕 先开个坑(其实是存模板来了) 一些特别简单的前置东西qwq 复数的计算 复数相加:向量相加,复数相乘.复数相乘:模长相乘,旋转量相加(就是复平面坐标轴逆时针旋转的角度) (当然也可以直接使用c ...

  5. OI字符串 简单学习笔记

    持续更新qwq KMP 其实是MP啦qwq 就是先自己匹配自己得到状态图,然后再在上面进行模式串的匹配. nxt数组返回的是以该节点结尾的,最长的,在前面出现过的,不相交的,字符串的最靠右的,末位位置 ...

  6. Log4j简单学习笔记

    log4j结构图: 结构图展现出了log4j的主结构.logger:表示记录器,即数据来源:appender:输出源,即输出方式(如:控制台.文件...)layout:输出布局 Logger机滤器:常 ...

  7. Linux——帮助命令简单学习笔记

    Linux帮助命令简单学习笔记: 一: 命令名称:man 命令英文原意:manual 命令所在路径:/usr/bin/man 执行权限:所有用户 语法:man [命令或配置文件] 功能描述:获得帮助信 ...

  8. <<C++标准程序库>>中的STL简单学习笔记

    0. 内容为个人学习笔记, 仅供参考, 如有错漏, 欢迎指正! 1. STL中的所有组件都是由模板构成的, 所以其元素可以是任意型别的. 组件有: - 容器: 管理某类对象的集合. 不同的容器有各自的 ...

  9. OI动态规划&&优化 简单学习笔记

    持续更新!! DP的难点主要分为两类,一类以状态设计为难点,一类以转移的优化为难点. DP的类型 序列DP [例题]BZOJ2298 problem a 数位DP 常用来统计或者查找一个区间满足条件的 ...

随机推荐

  1. firebug,chrome调试工具的使用

    ​http://ued.taobao.org/blog/?p=5534 chrome调试 http://www.cnblogs.com/QLeelulu/archive/2011/08/28/2156 ...

  2. Sqlserver2005中的varchar,varchar,char,nchar的比较

    C#窗体中的TextBox 的MaxLength:与Nvarchar类似,不论是什么,最多只能为2.我我11我1

  3. 在ios端点击按钮闪烁解决方法(小tips)

    在ios端,safari浏览器上触发click事件有300ms的延迟响应,为touch添加的样式会和click冲突而出现闪烁问题 在safari中触摸事件的相应顺序如下: touchstart --& ...

  4. Linux运维基础入门(一)网络基础知识梳理01

    一,计算机网络参考模型 1.1 OSI七层模型 1)物理层 主要功能是完成相邻节点之间原始比特流的传输.(网卡等) 物理层协议关心的典型问题是使用什么样的物理信号来表示数据1和0:持续的时间有多长:数 ...

  5. java tomcat报错: Starting Tomcat v7.0 Server at localhost' has encountered a problem问题

    运行web项目的时候出现下面错误: 出现这个问题的原因是 这个tomcat在其他项目中正在运行 关掉即可.

  6. linux shell脚本编程笔记(二): 分支结构

    1.if if command then commands fi if command then commands else commands fi if command1 then command ...

  7. Java-随机数工具类

    import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.la ...

  8. rtx 导出所有部门和用户

    1>添加Interop.RTXServerApi.dll的引用 2>RTXServerApi.RTXObjectClass和RTXServerApi.RTXObject实现一样,建议使用R ...

  9. Nginx 事件基本处理流程分析

    说明:本文章重点关注事件处理模型.有兴趣的同学可以去http://tengine.taobao.org/book/查找更多资料.Tengine应该是淘宝基于Nginx自己做的修改.这个地址的文档还在不 ...

  10. html5标签---不常用新标签的整理

    状态标签 meter 用来显示已知范围的标量值或者分数值. value:当前的数值. min:值域的最小边界值.如果设置了,它必须比最大值要小.如果没设置,默认为0 max:值域的上限边界值.如果设置 ...