有向图最小环问题

突然想起来一个问题…

此处仅提供求有向图最小环思路及算法(非AC算法)
正解请移步 题解

洛谷 P2661 信息传递
题目描述

有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。

游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?


一个同学从别人口中得到自己的信息,就是说他的信息打了个转之后回到他那里
只要有一个同学得到自己信息游戏就结束,也就是求那个打的转最小的信息传播过的人数

emmmm语文不是很好啊
总之题目简化为有向图最小环问题


floyd算法

原谅我图论不好什么都floyd…

先上代码

#include <iostream>
#include <cstdio>
#define INF 200001
//开过 0x7fffffff和 0x3f3f3f3f之后 一怒之下... 
//用 0x7fffffff或 0x3f3f3f3f的话一定要开long long 
using namespace std;
const int N=5005;
int n;
int ans;
int dis[N][N],g[N][N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			if(i==j)
				dis[i][j]=g[i][j]=0;
			else
				dis[i][j]=dis[j][i]=g[i][j]=g[j][i]=INF;
		}
	}
	int x;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&x);
		dis[i][x]=g[i][x]=1;
	}
	ans=INF;
	for(int k=1;k<=n;++k)
	{
		for(int i=1;i<k;++i)
		{
			for(int j=i+1;j<k;++j)
			{
				
				ans=min(ans,dis[j][i]+g[i][k]+g[k][j]);
				//注意是dis[j][i]不是dis[i][j] 
			}
		}
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=n;++j)
			{
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
} 

是不是跟无向图的很像qwq 最开始就是当无向图交了
不同主要是两点

  • 输入时的初始化(有向图嘛 不讲了)

  • dis[i][j]VSdis[j][i]

    我们假设i–>k–>j–>某条乱七八糟不经过k的路径–>i构成了一个环
    dis[j][i]代表j–>某条乱七八糟不经过k的路径–>i的距离
    g[i][k]+g[k][j]代表i–>k–>j的距离
    加起来就是上述环的长度

    那么dis[i][j]+g[i][k]+g[k][j])
    i–>另一条乱七八糟不经过k的路径–>j的距离加i–>k–>j的距离
    ???
    我就问下谁家环有一部分是倒着的

    这就是有向环和无向环的区别 \(≧▽≦)/


拓扑排序

拓扑排序是一种神奇的算法

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前

…我也感觉很抽象

  • 可以这样理解一个DAG
    一开始只有一个点,我们不断地加点和以它为起点的边,就形成了一个图(怎么跟建树有点像…)

    拓扑排序是反过来,把后加上的边和点一个个删除,按照点的删除顺序排成的点的序列,就是拓扑序

  • 那么怎么判断点的先后呢
    我们把入度为0的点看作最后加入的点

    因为加入一个点时,以它为起点的边只能指向在它之前加入的点,换句话说,一条边的终点一定是比起点先加入的
    那么刚加入的点,没有边指向它,它的入度一定为0

  • 拓扑排序的过程
    我们每次找到入度为0的点,删除它和以它为起点的边
    再重复这个过程,直到图空
    有没有很像bfs?

    另外,如果我们每次遍历整个图,查找入度为0的点,明显太费时了
    比较巧妙的方法是,删边同时记录下哪些点入度变为了0
    因为除第一次外,点的入度变为0一定是删边导致的,可以保证这样不会漏点

  • 一个DAG的形成过程一定是满足上述过程的,可以证明一个DAG经过拓扑排序后一定为空

  • 拓扑序的不确定性

    想象一下有一个点第一轮就加入了,然鹅没有点愿意和它玩,到最后它的入度还是为0,于是第一轮就被删掉了

    所以说入度为0的点只能看作最后加入的点

    其实这个点不仅入度为0,出度也为0
    仔细想想,这样的点不论哪一轮加入,对这个图都没有影响
    题目不告诉我们它是什么时候加入的,我们就永远不知道
    既然题目没有规定,我们说它是最后一轮加入的又有什么关系呢

    此外,我们是一个一个拓展的,可是一轮可能加入多个点,拓展的顺序不同也会导致拓扑序不同

说了这么多,拓扑排序跟最小环有什么关系呢

首先要明白拓扑排序删不了环
手动画一画就能发现,环是无法像DAG那样一个一个加入点的
环上的点出度入度都是没办法减到0的1

一个普通的有向图,可以看成若干个DAG和若干个环缠在一起
我们用拓扑把DAG都删掉,剩下的就全是环了qwq

本来应该用邻接表的,这里数据有点奇特而且每个点只出一条边,那就随意啦

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=200005;
int n;
int ans;
int in[N],out[N],a[N],q[N];
int vis[N];
inline int read()
{
    char c;
    int x=0,f=1;
    do{
        c=getchar();
        if(c=='-')
            f=-1;
    }while(c<'0' || c>'9');
    do{
        x=x*10+c-'0';
        c=getchar();
    }while(c>='0' && c<='9');
    return f*x;
}
inline void dfs(int x,int l)
{
    if(l>=ans)
        return ;
    if(vis[a[x]])
        ans=l;
    vis[x]=1;
    dfs(a[x],l+1);
    vis[x]=0;
}
int main(){
    n=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        out[i]++;
        in[a[i]]++;
    }
    int h=0,t=0;
    for(int i=1;i<=n;++i)
    {
        if(!in[i])
            q[++t]=i;
    }
    do{
        h++;
        in[a[q[h]]]--;
        if(!in[a[q[h]]])
            q[++t]=a[q[h]];	
    }while(h<t);
    ans=0x7fffffff;
    for(int i=1;i<=n;++i)
    {
        if(in[i])
            dfs(i,1);
    }
    printf("%d\n",ans);
    return 0;
}

  1. 这也可以解释为什么拓扑排序只适用于DAG
    为什么要无环已经解释了
    要有向是因为,无向图中有边相连的两点就能构成环 ↩︎

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值