在今天的课开始之前先道个歉……这几天我有事,所以三周没更贴,今天一起更(稿我都写好啦)
今天的主要知识点是图的连通性。先来一些干货,没有这个就没有tarjan算法的那种:
1.连通分量&强连通分量:一个有向图或无向图的最大连通子图,一个连通图的连通分量就是他本身。如果这个图不联通,每一个联通部分是一个连通分量。
2.强连通有向图:一般叫强连通图,意思是一个有向图的所有点都相连,可以理解为从任意一个点都能走到一个指定的位置。
我们这是又要接触一个东西了,他叫搜索树,就是一个保存搜索顺序的树。
跟搜索树相关的干货:
1,树枝边:一棵树里所有的边都是树枝边。
2,前向边:两个点与树上的顺序一致,而且图中有这个边,注意前向边是从父节点指向子节点的。
3,后向边:跟前向边反着来,从子节点接到父节点。
4,横叉边:搜索树的两个子树之间的边,注意横叉边一定是从右往左连得。
知道了这些东西,就能学习tarjan算法了。
首先要在知道两个干货:时间戳&最小时间戳。
时间戳可以理解为是搜索的顺序标签,最小时间戳就是从一个点走到当前点,那个点的时间戳比当前点要小,当前点的最小时间戳就是那个点的时间戳。
我们在搜图的时候不可能不会遇到环。一般的搜图算法基本上都是不过环的,有的算法一有环就不能用了,搜索时遇见环则会死循环。
但是tarjan算法允许有环,因为它采用了缩点技术。缩点就是把一个环给看成点,这样算法就都能用了。接下来实操一个题:
强连通分量
时间限制:1秒 内存限制:128M
题目描述
有一个 n 个点,m 条边的有向图,请求出这个图点数大于 1 的强联通分量个数。
输入描述
第一行为两个整数 n 和 m。
第二行至 m+1 行,每一行有两个整数 a 和 b,表示有一条从 a 到 b 的有向边。
输出描述
仅一行,表示点数大于 1 的强联通分量个数。
样例
输入
5 4 2 4 3 5 1 2 4 1
输出
1
提示
对于全部的测试点,保证 2≤n≤10^4,2≤m≤5×10^4,1≤a,b≤n。
解析:
这个就是很模板的tarjan算法,这个算法就是用来算连通分量的。
话不多说,上核心代码:
void tarjan(int u)
{
dfn[u]=low[u]=++t;
s[++top]=u;
vis[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(vis[j]) low[u]=min(low[u],dfn[j]);
}
if(dfn[u]==low[u])
{
int y;
sc++;
do{
y=s[top--];
vis[y]=false;
id[y]=sc;
sz[sc]++;
}while(y!=u);
}
}
int main()
{
//初始化
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
//输出
}
注意这个算法也是要用链式前向星的。以后我会专门抽出来一讲来说链式前向星。
再来一个比较灵活的提:
受欢迎的牛
时间限制:1秒 内存限制:512M
题目描述
每一头牛的愿望就是变成一头最受欢迎的牛。现在有 N 头牛,给你 M 对整数(A,B),表示牛 A 认为牛 B 受欢迎。这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。
输入描述
第一行两个数 N,M;
接下来 M 行,每行两个数 A,B,意思是 A 认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。
输出描述
输出被除自己之外的所有牛认为是受欢迎的牛的数量。
样例
输入
3 3 1 2 2 1 2 3
输出
1
提示
样例说明
只有第三头牛被除自己之外的所有牛认为是受欢迎的。
对于全部数据,1≤N≤10^4,1≤M≤5×10^4。
解析:
不太明显的tarjan。这个题要算谁是最受欢迎的牛,实际就是谁的入度最大。
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
for(int j=h[i];~j;j=ne[j])
{
int k=e[j],a=id[i],b=id[k];
if(a!=b) du[a]++;
}
}
int zero=0,ans=0;
for(int i=1;i<=sc;i++)
{
if(!du[i])
{
zero++;
ans+=sz[i];
if(zero>1)
{
ans=0;
break;
}
}
}
cout<<ans;
return 0;
}
只给主函数处理部分,tarjan函数跟上一个一模一样。
今天有点少,多数是干货。
作业题:
校园网
时间限制:1秒 内存限制:128M
题目描述
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入描述
输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出描述
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数,表示子任务 A 的解。
第二行应该包括一个非负整数,表示子任务 B 的解。
样例
输入
5 2 4 3 0 4 5 0 0 0 1 0
输出
1 2
老规矩,看到帖子务必点赞关注收藏,你们的每一个三联都是我前进的动力!