Popular Cows POJ - 2186(tarjan算法)+详解

题意:

每一头牛的愿望就是变成一头最受欢迎的牛。现在有 N头牛,给你M对整数(A,B),表示牛 A认为牛B受欢迎。这种关系是具有传递性的,如果 A认为 B受欢迎, B认为 C受欢迎,那么牛 A也认为牛 C受欢迎。你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。

题目:

Every cow’s dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.
Input

  • Line 1: Two space-separated integers, N and M

  • Lines 2…1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.
    Output

  • Line 1: A single integer that is the number of cows who are considered popular by every other cow.

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

Hint

Cow 3 is the only cow of high popularity.

思路:强连通——tarjan算法

即建立一个搜索树,运用tarjan算法进行缩点,最终建成一棵新树。
1.所有点只建成一棵树(u<=1)。
2.该树只有两个结果,即连通(u=1)和不连通(u=0)
3.在ac代码后,我会详细介绍tarjan算法。

AC代码(带步骤解释)

#include<stdio.h>//tarjan是一个缩点过程。
#include<string.h>
#include<algorithm>
using namespace std;
const int M=1e4+10;
const int N=5e4+10;
int to[N],nex[N],fir[M];
int col,num,dfn[M]/*时间戳:标记当前节点在深搜过程中是第几个遍历到的点*/;
int low[M]/*整个算法核心数组:每个点在这颗树中的,最小的子树的根*/;
int de[M]/*统计新建树的入度*/,si[M]/*统计新树中某节点(强连通分量)内包含多少个点*/;
int tot=0,co[M]/*表示新树的元素*/,n,m;
int top,st[M]/*栈*/;
void Ins(int x,int y)
{to[++tot]=y;nex[tot]=fir[x];///模拟链表fir[x]=tot;
}
void tarjan(int u/*当前节点*/)///tarjan缩点
{dfn[u]=low[u]=++num;//初始化st[++top]=u;//将u节点入栈for(int i=fir[u]; i; i=nex[i]) ///枚举每一条边。{int v=to[i]/*其能到达的节点*/;if(!dfn[v])//如果v点未被访问过{tarjan(v);//继续往下找low[u]=min(low[u],low[v]);}else if(!co[v]) ///判断是否在新树中,若不在需要对改点值更新。low[u]=min(low[u],dfn[v]);}if(low[u]==dfn[u])//某个节点回溯之后的low【u】值还是==dfn【u】的值,那么这个节点无疑就是一个关键节点(为强连通分量的一个顶点。){co[u]=++col;/*看做建了一个新树,只有用强连通分量的顶点建入树中*/++si[col];/*记录某连通分量内有多少个点*/while(st[top]!=u)//until u==v;(遍历该连通分量内有多少个点【在u前的点,均为一个连通分量】){++si[col];co[st[top]]=col;//建立新树,该联通分量内均为同一个时间戳。--top;//比此节点后进来的节点全部出栈}--top;//将u退栈。}
}
int main()
{scanf("%d%d",&n,&m);for(int i=1; i<=m; i++){int x,y;scanf("%d%d",&x,&y);Ins(y,x);///反向建边,统计入度。}for(int i=1; i<=n; i++)if(!dfn[i])tarjan(i);for(int i=1; i<=n; i++)for(int j=fir[i]; j; j=nex[j]) ///统计新树入度。{int v=to[j]; //i - > vint U=co[i];int V=co[v];  // U -> Vif(U!=V)//前面操作,使得联通分量内均为同一个时间戳(最小时间戳)de[V]++;}int ans=0,u=0;for(int i=1; i<=col; i++)if(!de[i])//入度不为零ans=si[i],u++;//此时新树中无连通分量if(u==1)//表明所有牛都欢迎printf("%d\n",ans);//(该新树联通分量包含几个点)else printf("0\n");//u==0,该树不连通;u>1,有多个树。return 0;
}

tarjan算法详解(以此题求解过程为例)

不知道怎样读tarjan,就去搜了一下,发现发明算法的不是中国人(挺正常?) 发明者:Robert Tarjan,所以我还是老老实实的叫塔尖算法吧。
在这里插入图片描述
看他和(残)蔼(酷)的脸,他发明的 tarjan算法,是一个关于图的联通性(将强连通分量【一堆点】)缩成一个点)的神奇算法。
基于DFS(迪法师)算法,深度优先搜索一张有向图。!注意!是有向图。根据树,堆栈,领接表等种种神奇方法来完成剖析一个图的工作。最后经过这些操作建成一个新树的过程。(缩点过程)思维千丝万缕,但代码却不长(妙哉)?orz废话不多说,来说下我理解的tarjan(说了,以此题为例)。

首先我们引入定义:

1、有向图G中,以顶点v为起点的弧的数目称为v的出度;以顶点v为终点的弧的数目称为v的入度。
2、如果在有向图G中,有一条<u,v>有向道路,则v称为u可达的,或者说,从u可达v。
3、如果有向图G的任意两个顶点都互相可达,则称图 G是强连通图,如果有向图G存在两顶点u和v使得u不能到v,或者v不能到u,则称图G是强非连通图。
4、如果有向图G不是强连通图,他的子图G2是强连通图,点v属于G2,任意包含v的强连通子图也是G2的子图,则乘G2是有向图G的极大强连通子图,也称强连通分量。
5、什么是强连通?强连通其实就是指图中有两点u,v。使得能够找到有向路径从u到v并且也能够找到有向路径从v到u,则称u,v是强连通的。

不妨引入一个图加强大家对强连通分量和强连通的理解:

在这里插入图片描述
标注棕色线条框框的三个部分就分别是一个强连通分量,也就是说,这个图中的强连通分量有3个。
其中我们分析最左边三个点的这部分:
其中1能够到达0,0也能够通过经过2的路径到达1.1和0就是强连通的。
其中1能够通过0到达2,2也能够到达1,那么1和2就是强连通的。

同理,我们能够看得出来这一部分确实是强连通分量,也就是说,强连通分量里边的任意两个点,都是互相可达的。
那么如何求强连通分量的个数呢?另外强连通算法能够实现什么一些基本操作呢?

即如何用Tarjan算法求强连通分量个数:

先来段伪代码
tarjan官方伪代码如下:

//parent为并查集,FIND为并查集的查找操作

//QUERY为询问结点对集合

//TREE为基图有根树

Tarjan(u)

visit[u] = true

for each (u, v) in QUERY

if visit[v]

ans(u, v) = FIND(v)

for each (u, v) in TREE

if !visit[v]

Tarjan(v)

parent[v] = u

本题伪代码
tarjan(u){DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值Stack.push(u)   // 将节点u压入栈中for each (u, v) in E // 枚举每一条边if (v is not visted) // 如果节点v未被访问过tarjan(v) // 继续向下找Low[u] = min(Low[u], Low[v])else if (v in S) // 如果节点u还在栈内Low[u] = min(Low[u], DFN[v])if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根repeat v = S.pop  // 将v退栈,为该强连通分量中一个顶点print vuntil (u== v)}

Tarjan算法,是一个基于Dfs的算法,假设我们要先从0号节点开始Dfs,我们发现一次Dfs我萌就能遍历整个图(树),而且我们发现,在Dfs的过程中,我们深搜到 了其他强连通分量中,那么如何判断哪个和那些节点属于一个强连通分量呢?我们首先引入两个数组:

①dfn[ ]
②low[ ]

第一个数组dfn我们用来标记时间戳:当前节点在深搜过程中是第几个遍历到的点
第二个数组是整个算法核心数组:经过运算后在这颗树中的,强连通分量内的的点的low【u】值均为强连通分量顶点的时间戳(以判断low【u】的值是否一样来判断是否为同一个强连通变量)
这个时候我们不妨在纸上画一画写一写,搞出随意一个Dfs出来的dfn数组来观察一下(假设我们从节点0开始的Dfs,其中一种可能的结果是这样滴):
在这里插入图片描述
这个时候我们回头来看第二个数组要怎样操作,我们定义low【u】=min(low【u】,low【v】(即使v搜过了也要进行这步操作,但是v一定要在栈内才行)),

  • (1) u代表当前节点,v代表其能到达的节点。

  • (2)这个数组在刚刚到达节点u的时候初始化low【u】=dfn【u】

  • (3)然后在进行下一层深搜之后回溯回来的时候,维护low【u】。
    1)如果如果v点未被访问过Low[u] = min(Low[u], Low[v])
    2)如果如果v点被访问过,且未被建在新树中。

  • (4)如果我们发现了某个节点回溯之后的**low【u】值==dfn【u]**的值,那么这个节点无疑就是一个关键节点(为强连通分量的一个顶点):从这个节点能够到达其强连通分量中的其他节点,但是没有其他属于这个强连通分量以外的点能够到达这个点,所以这个点的low【u】值维护完了之后还是和dfn【u】的值。

上图运行一遍的各个数值的变化。

①首先进入0号节点,初始化其low【0】=dfn【0】=1,然后深搜到节点2,初始化其:low【2】=dfn【2】=2,然后深搜到节点1,初始化其:low【1】=dfn【1】=3,将其全部进栈;

②然后从节点1开始继续深搜,发现0号节点已经搜过了,没有继续能够搜的点了,开始回溯维护其值。low【1】=min(low【1】,low【0】)=1;low【2】=min(low【2】,low【1】)=1;low【0】=min(low【0】,low【2】)=1;

③这个时候虽然low【0】==dfn【0】,但不能断定0号节点是一个关键点,别忘了,这个时候还有3号节点没有遍历,我们只有在其能够到达的节点全部判断完之后,才能够下结论,所以我们继续Dfs。

④继续深搜到3号节点,初始化其low【3】=dfn【3】=4,然后深搜到4号节点,初始化其:low【4】=dfn【4】=5,将两个点都进栈。这个时候发现深搜到底,回溯,因为节点4没有能够到达的点,所以low【4】也就没有幸进行维护即:low【4】=dfn【4】出栈,进入新树co[4]=1(这个点一定是强连通分量的关键点,但是我们先忽略这个点,这个点没有代表性,一会分析关键点的问题),然后回溯到3号节点,low【3】=min(low【3】,low【4】)=4;(由于连通性质,回溯时,low【3】的时间戳一定小于low【4】,low【3】是一个关键点,不会改变)发现low【3】==dfn【3】出栈,co[3]=2(那么这个点也是个关键点,我们同样忽略掉。)

⑤最终回溯到节点0,进行最后一次值的维护:low【0】=min(low【0】,low【3】)=0,这个时候我们猛然发现其dfn【0】==low【0】,根据刚才所述,那么这个点就是一个关键点:能够遍历其属强连通分量的点的起始点,而且没有其他点属于其他强连通分量能够有一条有向路径连到这个节点来的节点,此时栈顶元素为2,则栈内在0上的所有点包括零都为同一个强连通分量,出栈进入新树co[2]=3,co[1]=3,co[0]=3.

大家仔细理解一下这句话,因为这个点属于一个强连通分量,而且强连通分量中的任意两个节点都是互达的,也就是说强连通分量中一定存在环,这个最后能够回到0号节点的1号节点一定有机会维护low【1】,因为0号节点是先进来的时间戳一定小,所以其low【1】的值也一定会跟着变小,然后在回溯的过程中,其属一个强连通分量的所有点都会将low【u】值维护成low【0】,所以这个0号节点就是这个关键点:能够遍历其属强连通分量的起始点而且这样的起始点一定只有一个,所以只要发现了一个这样的关键起始点,那么就一定发现了一个强连通分量。而且这个节点没有其他点属于其他强连通分量能够有一条有向路径连到这个节点来的节点:如果这样的点存在,那么这些个点应该属于同一个强连通分量。

那么综上所述,相信大家也就能够理解为什么dfn【u】==low【u】的时候,我们就可以判断我们发现了一个强连通分量了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309953.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[设计模式]装饰模式

装饰模式: 通过AbstractEquipment装饰AbstractHero&#xff0c;使其heroA增加了一个穿装备的功能。 代码如下: #include <iostream> using namespace std;class AbstractHero {public:virtual void showStatus() 0;int hp;int mp;int at;int df; };class HeroA :publi…

ASP.NET Core分布式项目实战(Consent Controller Get请求逻辑实现)--学习笔记

任务20&#xff1a;Consent Controller Get请求逻辑实现接着上一节的思路&#xff0c;实现一下 ConsentController根据流程图在构造函数注入 IClientStore&#xff0c;IResourceStore&#xff0c;IIdentityServerInteractionService构造函数private readonly IClientStore _cli…

[设计模式]观察者模式

代码如下: #include <iostream> #include <list> using namespace std;class AbstractHero { public:virtual void update() 0; };class HeroA :public AbstractHero { public:HeroA(){cout << "英雄A正在打BOSS" << endl;}virtual void u…

RMQ算法讲解

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/qq_41311604/article/details/79900893 </div><!--一个博主专栏付费入口--><!--一个…

Kubernetes是容器化微服务的圣杯么?

导语Kubernetes已成为山丘之王。开源技术Kubernetes以及随后的发行版正以超快的速度让人们爱上容器技术&#xff0c;并且开始夺回对容器化环境的控制权。不幸的是&#xff0c;编排容器只是战斗进行了一半。正文云服务提供商接连宣布他们的编排选择是Kubernetes私有发行版&#…

[设计模式]命令模式

代码如下: #include <iostream> #include <queue> #include <Windows.h> using namespace std;class HandleClientProtocol { public:void addMoney(){cout << "给玩家增加金币" << endl;}void addDiamond(){cout << "给玩…

Zjnu Stadium HDU - 304 加权并查集

题意&#xff1a; 观众席围成一圈。列的总数是300&#xff0c;编号为1–300&#xff0c;顺时针计数&#xff0c;我们假设行的数量是无限的。将有N个人去那里。他对这些座位提出了要求&#xff1a;这意味着编号A的顺时针X距离坐着编号B。例如&#xff1a;A在第4列&#xff0c;X…

还不明白可空类型原理? 我可要挖到底了

一&#xff1a;背景1. 讲故事做好自媒体到现在有一个月了&#xff0c;关注我的兄弟应该知道我产出了不少文章&#xff0c;号里的粉丝也多起来了&#xff0c;我也尽最大努力做到有问必回&#xff0c;现在是基础的、高深的问题都接踵而来&#xff0c;可我也只是一只小菜鸟&#x…

[设计模式]策略模式

策略模式:定义了一系列算法&#xff0c;并将每一个算法封装起来&#xff0c;而且使它们还可以相互替换。 策略模式让算法独立于使用它的客户而独立变化。 代码如下: #include <iostream> using namespace std;class WeaponStrategy { public:virtual void useWeapon()…

[设计模式]模板方法模式

模板方法模式: 定义一个操作中算法的框架&#xff0c;而将一些步骤延迟到子类中。模仿方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 代码如下: #include <iostream> using namespace std;class DrinkTemplate { public:virtual void Boi…

差距(分享)

非985大学生, 你和别人的差距在哪里?&#xff08;转&#xff09; 非985大学生, 你和别人的差距在哪里? 中国青年报03-24 在知乎上看到这样一段话&#xff1a; “渣学校意味着渣教学&#xff0c;渣教学意味着渣学历&#xff0c;渣学历意味着渣就业&#xff0c;就算以后考了研究…

在微服务框架Demo.MicroServer中添加SkyWalking+SkyApm-dotnet分布式链路追踪系统

1.APM工具的选取Apm监测工具很多&#xff0c;这里选用网上比较火的一款Skywalking。Skywalking是一个应用性能监控(APM)系统&#xff0c;Skywalking分为服务端Oap、管理界面UI、以及嵌入到程序中的探针Agent部分&#xff0c;大概工作流程就是在程序中添加探针采集各种数据发送给…

计算机组成原理期末复习题

地址总线A15~Ao(低),存储空间(按字节编址)分配如下 2000H~3FFFH为ROM区, 5000H~6FFFH为RAM区。用 ROM芯片(4Kx4)和RAM芯片(4Kx4)组成该存储器。请回答 &#xff1a;(1)分别需要ROM和RAM多少片? (2)用二进制形式写出每组芯片的地址范围,并说明可以通过哪些地址位来形成片选信号…

干货分享:如何使用Kubernetes的Ingress API

导语以Kubernetes的Kong为例&#xff0c;聊聊当前流行的开源且与云无关的Ingress控制器。正文您可以通过使用诸如Kong for Kubernetes的Ingress控制器&#xff08;使用自定义资源定义并提供许多插件&#xff09;来极大地扩展Ingress资源的功能。Kubernetes正在整个技术行业中得…

计算机组成原理期末复习往年卷子

1. I/O设备的编址方式通常有___统一编址__和_独立编址__两种方式。P145 2&#xff0e;Cache是一种高速缓冲存储器&#xff0c;是为了解决____CPU____和___主存____之间速度不匹配而采用的一项重要技术。P124 3&#xff0e;在计算机系统中&#xff0c;I/O设备与主机传递消息的…

.NET Core接入ElasticSearch 7.5

写在前面最近一段时间&#xff0c;团队在升级ElasticSearch&#xff08;以下简称ES&#xff09;&#xff0c;从ES 2.2升级到ES 7.5。也是这段时间&#xff0c;我从零开始&#xff0c;逐步的了解了ES&#xff0c;中间也踩了不少坑&#xff0c;所以特地梳理和总结一下相关的技术点…

[C++11]字符串原始字面量

代码如下: #include <iostream> #include <string> using namespace std;int main() {string str1 R"(D:\hello\world\test.txt)";cout << str1 << endl;string str2 R"(dsdasasdasasda asdagdfhadagd)";cout << str2 &l…

ASP.NET Core on K8s学习之旅(13)Ocelot API网关接入

【云原生】| 作者/Edison Zhou这是恰童鞋骚年的第232篇原创文章上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使用&#xff0c;考虑到很多团队都在使用Ocelot作为API网关&#xff08;包括我司&#xff09;做了很多限流和鉴权的工作&#xff0c;因此本篇介绍一下如…

数据结构期末复习

1.完全二叉树的第5层有9个节点&#xff0c;该完全二叉树总计有多少个节点( B ). A.41 B.24 C.40 D.25 2.具有21个顶点的无向图至少有多少条边才能形成连通图 ( B ). A.21 B.20 C.22 D.21…

C++实现拓扑排序(vector模拟邻接表存储,优先队列实现)

代码如下: #include <iostream> #include <queue> #include <vector> using namespace std; const int N 10010; int in[N]; vector<int>v[N]; vector<int>print;//存放拓扑序列 int main() {int n, m;//n为点的个数&#xff0c;m为边的条数,点…