点此看题面
大致题意:有
N
N
个点,第个点有一个值
Nexti
N
e
x
t
i
,规定了你到达编号为
i
i
的节点后下一个要到的节点。每当你到达一个已经经过的节点后,就会结束遍历。问你从编号为的每一个节点出发所能经过的点数。
对于只有环的的情况
假设这张图上只有环,那么这道题应该就很简单了,直接 Tarjan T a r j a n 求出每一个节点所在环的环长即可(出度入度皆为1的情况下,强连通分量只有环)。
Link
Tarjan详见博客用Tarjan来实现强连通分量缩点
考虑如何处理链
好吧,可惜这张图上不仅有环,还有链。
那么对于链怎么处理呢?
多画画图,就可以发现,一条链最后肯定连向一个环,也就是说,对于链的情况,答案就是从当前节点到达某一个环的距离再加上这个环的环长。
具体实现
既然这样,我们还是先
Tarjan
T
a
r
j
a
n
求出每一个强连通分量的大小,不难发现,每一个强连通分量中的所有节点最终答案都是一样的。
因此,对于每一个强连通分量,我们可以
dfs
d
f
s
搜出它的答案(不停往下搜,每经过一个强连通分量就将
ans
a
n
s
加上这个强连通分量的大小)。
但是,只要数据够强,这样的方法还是会被卡成
O(n2)
O
(
n
2
)
。
怎么办呢?
我们可以考虑记忆化。
对于每一个强连通分量,可以用
ansi
a
n
s
i
直接记录这个强连通分量中每一个节点的答案,以后访问到这个强连通分量,直接返回
ansi
a
n
s
i
即可,就不用继续往下搜了。
这样一来,复杂度就是
O(n)
O
(
n
)
了。
代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define add(x,y) (e[++ee].to=y,e[ee].nxt=lnk[x],lnk[x]=ee)
#define nadd(x,y) (ne[++nee].to=y,ne[nee].nxt=nlnk[x],nlnk[x]=nee)
#define N 100000
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
int n,m,ee=0,nee=0,cnt=0,d=0,top=0,lnk[N+5],nlnk[N+5],dfn[N+5],low[N+5],col[N+5],vis[N+5],Stack[N+5],tot[N+5],ans[N+5];
struct edge
{
int to,nxt;
}e[2*N+5],ne[2*N+5];
queue<int> q;
inline void read(int &x)
{
x=0;int f=1;static char ch;
while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
x*=f;
}
inline void write(int x)
{
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
inline void Tarjan(int x)//Tarjan预处理,并求出每个强连通分量的大小
{
register int i;
dfn[x]=low[x]=++d,vis[Stack[++top]=x]=1;
for(i=lnk[x];i;i=e[i].nxt)
{
if(!dfn[e[i].to]) Tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]);
else if(vis[e[i].to]) low[x]=min(low[x],low[e[i].to]);
}
if(!(dfn[x]^low[x]))
{
vis[x]=0,tot[col[x]=++cnt]=1;//用tot统计该强连通分量大小
while(Stack[top]^x) vis[Stack[top]]=0,col[Stack[top--]]=cnt,++tot[cnt];
--top;
}
}
inline void dfs(int x)//记忆化搜索
{
register int i;
for(ans[x]=tot[x],i=nlnk[x];i;i=ne[i].nxt)//枚举它能到达的强连通分量(其实就一个)
{
if(!ans[ne[i].to]) dfs(ne[i].to);//如果这个强连通分量没有访问过,就求出这个强连通分量的答案
ans[x]+=ans[ne[i].to];//将当前强连通分量的答案加上它能到达的强连通分量的答案
}
}
int main()
{
register int i,j,x,y;
for(read(n),i=1;i<=n;++i) read(x),add(i,x);
for(i=1;i<=n;++i) if(!dfn[i]) Tarjan(i);
for(i=1;i<=n;++i)
for(j=lnk[i];j;j=e[j].nxt)
if(col[i]^col[e[j].to]) nadd(col[i],col[e[j].to]);
for(i=1;i<=n;++i)//枚举每一个节点,求出并输出答案
{
if(!ans[col[i]]) dfs(col[i]);//如果当前节点所在的强连通分量没有被访问过,就访问该强连通分量并求出答案
write(ans[col[i]]),pc('\n');//输出
}
return fwrite(pp,1,pp_,stdout),0;
}
后记
做完才发现,因为每个点入度出度皆为1,所以我这个做法真的太麻烦了,完全可以不 Tarjan T a r j a n 直接记忆化搜索,说不定还跑得更快。