讲解这道题之前,先来介绍一下Tarjan吧,这里只介绍最基础的强联通版本。
一、强联通概念
连通:两个点之间有路径互相可达。
连通图:任意两点都连通的无向图。
连通块/连通分量:极大连通子图。
强连通:有向图中,任意两个点都可以相互到达。
弱连通:无向图中,任意两个点都可以相互到达(有向图无法达成)。
强连通分量:有向图的极大强连通子图。
即:选择一些节点以及它们的所有边,这些节点和边构成的子图是强连通的。
性质:每个节点都在一个强连通分量中,至少自己单独形成一个强连通分量。如果a,b可相互到达,a,c可相互到达,则b,c也可以相互到达,a,b,c同属于一个强连通分量。
二、Tarjan操作
1.在无向图上DFS,每个节点只经过一次,搜索走过的边(红色),形成一棵树。搜索走过的称为“树边”,其他边是“非树边”。
2.如果u是强连通分量中首个被搜到的节点,则强连通分量都在u的子树中。因为如果v不在子树中,则有向图上无法从u到达v,矛盾。将u节点称为所在强连通分量的根节。
Tarjan算法:DFS返回到在强连通分量的根节点时,发现并删除这个强连通分量。
三、Tarjan模板
结构体+建边+Tarjan
变量声明:
dfn[u]:节点u是DFS时第几个被访问到的节点,数值范围从1到n。dfn[u]<子树内每个节点的dfn
low[u]:从u子树内经过一条非树边,能够到达节点的最小dfn值。
low[u]≤dfn[u]
初值为dfn[u],在DFS从儿子节点返回时用low[fa]更新low[u]。
节点栈:
将搜索访问到的节点存入栈中,每当找到一个强连通分量,就从栈中删除这个强连通分量的节点。
struct node { ll from,to,nxt; } a[]; void add(ll u,ll v) { k++; a[k].from=u; a[k].to=v; a[k].nxt=pre[u]; pre[u]=k; }//链表存储图 stack<ll> s;//栈,用来储存一个强连通分量里的所有点 void tarjan(ll u) { dfn[u]=low[u]=++cnt;//每遍历到一个点,更新遍历的次数 s.push(u);//节点u入栈 for (ll v=pre[u];v;v=a[v].nxt)//边的变化,边u->边v { if (!dfn[a[v].to]) tarjan(a[v].to),low[u]=min(low[u],low[a[v].to]);//如果当前没有去过v节点,DFS遍历v节点,同时用u更新v的low的值 else if (!c[a[v].to]) low[u]=min(dfn[a[v].to],low[u]);//如果v节点已经在栈中了,用dfn更新low的值 } if (dfn[u]==low[u])//寻找到一个强连通分量 { color++;//统计强连通分量数量 while (t!=u)//将栈清空 { ll t=s.top();//栈内最顶端的点 s.pop();//弹出栈内的点 c[t]=color;//栈内的所有点全部赋上值 } } }
[NOIP2015] 信息传递 | |
|
问题描述
有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti的同学。
游戏开始时,每个人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入格式
输入共2行。
第1行包含1个正整数n,表示n个人。
第2行包含n个用空格隔开的正整数T1,T2,...,Tn,其中第i个整数Ti表示编号为i的同学的信息传递对象是编号为Ti的同学,Ti<=n且Ti≠i。
数据保证游戏一定会结束。
输出格式
输出共1行,包含1个整数,表示游戏一共可以进行多少轮。
样例输入
样例输入:
5
2 4 2 3 1
样例输出
样例输出:
3
题意大概可以理解为,每个人(节点)在每一轮都会将自己知道的生日日期告诉自己熟悉的人(建边传递),问在第几轮会有人从别人口中知道自己的生日日期。
可以传递信息的两人之间可以建一条有向边,许多有向边可以构成一个有向图。因为会有人从别人口中知道自己的生日日期,所以人之间传递信息的路线便可以形成有一个闭环,也就是强联通分量。
问最终的轮数,也就是求最小强联通分量里面点的个数。注意节点数为1的强联通分量不行,因为一个人无法从别人口中知道自己的生日日期。
然后问题就简单化了,先建边,逐个Tarjan查找,把每个强联通分量的节点数逐一比较,找到最小且不唯一的。
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,k,cnt,cl,t,x,y,minn=200001,id;
ll pre[200001],dfn[200001],low[200001],c[200001],f[200001];
struct node
{
ll from,to,nxt;
} a[200001];
void add(ll u,ll v)
{
k++;
a[k].to=v;
a[k].nxt=pre[u];
pre[u]=k;
}
stack<ll>s;
void tarjan(ll u)
{
dfn[u]=low[u]=++cnt;
s.push(u);
for (ll v=pre[u];v;v=a[v].nxt)
{
if (!dfn[a[v].to]) tarjan(a[v].to),low[u]=min(low[u],low[a[v].to]);
else if (c[a[v].to]==0) low[u]=min(dfn[a[v].to],low[u]);
}
if (dfn[u]==low[u])
{
cl++;
ll t;
while (1)
{
t=s.top();
s.pop();
c[t]=cl;
if(t==u) break;
}
}
}
int main()
{
scanf("%lld",&n);
for (ll i=1;i<=n;i++)
{
ll x;
scanf("%lld",&x);
add(i,x);
}
for (ll i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
for (ll i=1;i<=n;i++) f[c[i]]++;
for (ll i=1;i<=cl;i++) if (minn>f[i]&&f[i]>1) minn=f[i];
printf("%lld",minn);
}
Ps:这一课学得不是很明白,如果有什么不足之处请多请教。