365bet开户网-365bet提款审核-365玩球平台

图上找环

前言 环是普通图区别于部分特殊图的原因,学会在图上高效且正确的找到或者判断各种环是有必要的。 本文介绍作者见过的那些有向图,无向

图上找环

前言

环是普通图区别于部分特殊图的原因,学会在图上高效且正确的找到或者判断各种环是有必要的。

本文介绍作者见过的那些有向图,无向图上各种找环判环问题和解决方案。

可通过点选右侧导航栏来翻看你想要的内容。

work in progress...

图上环的定义

如果你直接看一个图那么环的定义是显然的。

但是形式化定义还是要给一下的:

给定图 \(G = (V, E)\),一个简单环是顶点序列 \(v_0, v_1, \ldots, v_k\) 满足:

\(v_0 = v_k\)(闭合),

对任意 \(0 \leq i < j \leq k\),若 \(i \neq 0\) 或 \(j \neq k\),则 \(v_i \neq v_j\)(顶点不重复),

边集 \(\{v_i v_{i+1} \mid 0 \leq i < k\}\) 无重复。

有向图中的环都是有方向的,你可以认为无向图中的环是没有方向的。

判断有向图中是否存在环

当然可以简单的用一遍 DFS 判断,但是递归常数大。

Show me the code

#include

using namespace std;

const int N=1e5+5;

vector edge[N];

bool vis[N];

bool dfs(int u){

for(auto v:edge[u]){

if(vis[v])return 1;

vis[v]=1;

if(!vis[v]&&dfs(v))return 1;

}

return 0;

}

int main(){

int n,m;

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

cin>>u>>v;

edge[u].push_back(v);

}

for(int i=1;i<=n;i++){

if(!vis[i]&&dfs(i)){

cout<<"circuit";

return 0;

}

}

cout<<"no";

return 0;

}

还记得拓扑排序吗,如果拓扑排序可以遍历到所有的点,也可以证明原图没有环,反之也可以证明原图有环。

Show me the code

#include

using namespace std;

const int N=1e5+5;

vector edge[N];

int inner[N];

int main(){

int n,m;

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

cin>>u>>v;

edge[u].push_back(v);

inner[v]++;

}

queue q;

for(int i=1;i<=n;i++){

if(inner[i]==0)q.push(i);

}

int cnt=0;

while(q.size()){

int u=q.front();q.pop();

cnt++;

for(auto v:edge[u]){

inner[v]--;

if(inner[v]==0)q.push(v);

}

}

if(cnt!=n)cout<<"circuit";

else cout<<"no";

return 0;

}

判断无向图中是否存在环

当然可以简单的用一遍 DFS 判断,但是递归常数大。

Show me the code

#include

using namespace std;

const int N=1e5+5;

vector edge[N];

bool vis[N];

bool dfs(int u){

for(auto v:edge[u]){

if(vis[v])return 1;

vis[v]=1;

if(!vis[v]&&dfs(v))return 1;

}

return 0;

}

int main(){

int n,m;

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

cin>>u>>v;

edge[u].push_back(v);

edge[v].push_back(u);

}

for(int i=1;i<=n;i++){

if(!vis[i]&&dfs(i)){

cout<<"circuit";

return 0;

}

}

cout<<"no";

return 0;

}

考虑环上的每一个点在无向图上至少拥有两个度,因此类似拓扑排序,我们每次循环去掉度小于二的点,并对周围它的临界点的度数减。如果在这样的情况下能够遍历到所有点,就可以证明原图中没有环。

注意因为是无向图要另外开一个数组记录各点是否被删除,避免死掉。

Show me the code

#include

using namespace std;

const int N=1e5+5;

vector edge[N];

int inner[N];bool del[N];

int main(){

int n,m;

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

cin>>u>>v;

edge[u].push_back(v);

edge[v].push_back(u);

inner[v]++;inner[u]++;

}

queue q;

for(int i=1;i<=n;i++){

if(inner[i]<=1){q.push(i);del[i]=1;}

}

int cnt=0;

while(q.size()){

int u=q.front();q.pop();

cnt++;

for(auto v:edge[u]){

if(del[v])continue;

inner[v]--;

if(inner[v]<=1){q.push(v);del[v]=1;}

}

}

if(cnt!=n)cout<<"circuit";

else cout<<"no";

return 0;

}

找图的最小环长

一个简单环的环长定义为这个环上的边的边权之和。

这个东西目前没什么高妙的做法,暴力的尝试断开每一条有向边 \((i,j)\),计算在断开这个边后的图上 \(i\) 到 \(j\) 的最短路,那么加上这个边就是一个经过 \(i,j\) 的最小环了。

这样,图上每个边都断一遍,取最小即得图中最小环长。

如果图比较特殊,可以使用对应比较特殊的求解最短路的方法求解最短路。

若使用优先队列优化的 Dijkstra 算法,时间复杂度为 \(O(m^2 \log m)\)

如果是在无向图上,一次断掉两条边即可(尽管一次断掉一条边也是对的)。辛苦点用链式前向星存图就可以一次断掉两条边了。

当然,由于这个算法基于边,因此还是用对边管理比较准确的链式前向星存图比较好。

下面的代码例是在边权恒定为 \(1\) 的无向图上,用了一半(?)的链式前向星存图的找最小环算法。

Show me the code

#define rd read()

#include

using namespace std;

typedef long long ll;

int n,m;

const int N=7000;

struct e{

int u,v,eid;

}ei[N*2];

vector edge[N];

ll sakanaction=0;

void bfs(int s,int ban,int t){

queue q;q.push(s);

memset(dii,0x3f,sizeof dii);

dii[s]=0;

while(q.size()){

int u=q.front();

if(u==t)break;

q.pop();

for(int i=0;i

int v=edge[u][i].v;

int eid=edge[u][i].eid;

if (eid==ban) continue;// 不能通过被断掉的边

if(dii[v]>dii[u]+1){

dii[v]=dii[u]+1;

q.push(v);

}

}

}

return ;

}

int main(){

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

u=rd;v=rd;

edge[u].push_back(e{u,v,i});edge[v].push_back({v,u,i*2});

ei[i].u=u;ei[i].v=v;ei[i].eid=i;

ei[i+m].u=v;ei[i+m].v=u;ei[i+m].eid=i*2;

}

int k=0x3f3f3f3f;

for(int i=1;i<=m*2;i++){

bfs(ei[i].u,ei[i].eid,ei[i].v);

k=min(k,abs(dii[ei[i].u]-dii[ei[i].v])+1);

}

cout<

return 0;

}

图上最小环计数

NCPC 2022 E. Enigmatic Enumeration

首先先应用上面的找最小环长算法找到这个图的最小环长。

你会发现如果想要一次就不重不漏的找到所有环是很难做的,因此我们试试先不考虑重复,找到从每一点开始的最小环的数量。

因为最小环上有环长这么多个点,因此每个环一定会被多统计环长遍,把这样计算出的环数量除以环长就是答案。

现在考虑从一个点出发计算经过这个点的最小环数量,这好像依然非常难办。我们一般处理环问题都是断环成链的,因此这里我们把这个环分成二分之环长的两部分。这里涉及一个奇偶性讨论,为了方便我们先让环长 \(k\) 是个偶数,这样环就被我们分解为两条长度为 \(\frac{k}{2}\) 的简单路径。

你会发现这两条简单路径都是从指定的点出发的,并且会与另外一点。

这让我们想到,可以考虑计算从这一点出发,到每个点的长度为 \(\frac{k}{2}\) 的不同简单路径的条数,这样就可以应用乘法原理计算可能的环的数量了。

细节上说,我们从选定点的每一个出点开始寻找长度为 \(\frac{k}{2}\) 的路径,这一过程 DFS 是好做的。经过 \(\frac{k}{2}\) 找到一点时,这个点之前记录的路径数就都不是从这个出点出发的了,因此可以大胆认为组成了路径数量的环,然后累加但不并入之前记录的路径数。当前出点遍历完后,再并入之前记录的路径数。

现在你应该明白大致原理了。若环长为奇数,只需要统计 \(\frac{k}{2}\) 路径数,但再走到 \(\frac{k}{2}+1\) 长度的路径时再计算即可。

code

Show me the code

#define rd read()

#include

using namespace std;

typedef long long ll;

ll read(){

ll x=0,f=1;

char c=getchar();

while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}

while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}

return x*f;

}

int n,m;

const int N=7000;

struct e{

int u,v,eid;

}ei[N*2];

int dii[N];

vector edge[N];

ll sakanaction=0;

void bfs(int s,int ban,int t){

queue q;

q.push(s);

memset(dii,0x3f,sizeof dii);

dii[s]=0;

while(q.size()){

int u=q.front();

if(u==t)break;

q.pop();

for(int i=0;i

int v=edge[u][i].v;

int eid=edge[u][i].eid;

if (eid==ban) continue;

if(dii[v]>dii[u]+1){

dii[v]=dii[u]+1;

q.push(v);

}

}

}

return ;

}

int vis[N];

int toti[N],subr[N];

void dfs1(int u,int d,int md){

if(d==md){subr[u]++;return ;}

for(int i=0;i

int v=edge[u][i].v;

if(vis[v])continue;

vis[v]=1;

dfs1(v,d+1,md);

vis[v]=0;

}

}

void dfs2(int u,int d,int md){

if(d==md){subr[u]++;return ;}

else if(d==md-1){sakanaction+=toti[u];}

for(int i=0;i

int v=edge[u][i].v;

if(vis[v])continue;

vis[v]=1;

dfs2(v,d+1,md);

vis[v]=0;

}

}

int main(){

cin>>n>>m;

for(int i=1;i<=m;i++){

int u,v;

u=rd;v=rd;

edge[u].push_back(e{u,v,i});edge[v].push_back({v,u,i*2});

ei[i].u=u;ei[i].v=v;ei[i].eid=i;

ei[i+m].u=v;ei[i+m].v=u;ei[i+m].eid=i*2;

}

int k=0x3f3f3f3f;

for(int i=1;i<=m*2;i++){

bfs(ei[i].u,ei[i].eid,ei[i].v);

k=min(k,abs(dii[ei[i].u]-dii[ei[i].v])+1);

}

if(k%2==0){

for(int i=1;i<=n;i++){

memset(toti,0,sizeof toti);

vis[i]=1;

for(int j=0;j

int v=edge[i][j].v;

memset(subr,0,sizeof subr);

dfs1(v,1,k/2);

for(int pp=1;pp<=n;pp++){

sakanaction+=toti[pp]*subr[pp];

toti[pp]+=subr[pp];

}

}

vis[i]=0;

}

}

else{

for(int i=1;i<=n;i++){

memset(toti,0,sizeof toti);

vis[i]=1;

for(int j=0;j

int v=edge[i][j].v;

memset(subr,0,sizeof subr);

dfs2(v,1,k/2+1);

for(int pp=1;pp<=n;pp++){

toti[pp]+=subr[pp];

}

}

vis[i]=0;

}

}

cout<

return 0;

}

图中判断是否存在奇环偶环

还记得二分图的一个性质吗:

理论判定:不存在奇数长度回路的图一定是二分图。特殊的:没有环的图也一定是二分图

于是判断二分图的过程就是判断奇环的过程。黑白染色法即可。

Show me the code

#include

using namespace std;

const int N=1e5+5;

vector edge[N];

int col[N];

bool dfs(int u,int c){

for(auto v:edge[u]){

if(!col[v]){

col[v]=3-c;

if(dfs(v,3-c))return 1;

}

else if(col[v]!=3-c)return 1;

}

return 0;

}

int main(){

int n,m;

for(int i=1;i<=m;i++){

int u,v;cin>>u>>v;

edge[u].push_back(v);

edge[v].push_back(u);

}

for(int i=1;i<=n;i++){

if(!col[i]&&dfs(i,1)){

cout<<"odd";

return 0;

}

}

cout<<"even"<<' ';

return 0;

}

判偶环,那么如果图里没有奇环但是有环那么就是偶环了。

基环树上找环

无向图中的简单环计数

无向图中的三元环计数

无向图中的四元环计数

← 上一篇: 中国最贵的炮弹:打出一发,就相当于一辆宝马X7在天上飞
下一篇: 金山毒霸护眼模式使用指南 →

相关推荐

电脑隐私安全必备,免费Windows文件/文件夹隐藏+加密工具
2025年1月15日万年历,农历甲辰年十二月十六日黄历查询,2025.1.15日历查询宜忌吉凶
盘点8部惨遭封禁的尺度日漫,最后一部是公认的神作!
《天谕手游》哪个老区人多物价便宜?2024年人多服务器推荐及买号攻略
欧根亲王
适马镜头和佳能镜头哪个好报价多少选购技巧2025/06/26 20:56:19收藏
活力四射的意思
男生亲着亲着就会起反应吗?性健康专家揭秘:3种情况很正常
蒜苗的做法大全(蒜苗的九种家常做法!)
雷凌的天线在哪里里?
王者荣耀信誉积分机制及恢复攻略
管理的本质是什么——理解管理的核心与价值