题目链接:TZOJ:2592Trick or Treat on the Farm
题面:
样例输入:
4
1
3
2
3
样例输出:
1
2
2
3
该题的图和TZOJ:6024信息传递的图几乎一模一样;
就是在信息传递的基础上加了个自环的情况。
题中要求从每个点出发直至走到走过的点的时候的最大点数。
可以发现每个点的出度一定是1,所以每个点一定会一直向外走直至走到环。
由于对有向图进行tarjan缩点后一定会变成一个拓扑图。那么可发现由于每个点一定会向外延申直至到环,所以这个拓扑图最终的那个点是一个环(可能是自环,也有可能是多个点形成的环),而前面的那几个点一定是只有一个点的强连通分量。
所以用sz数组记录每个强连通分量中的点数,然后再跑一边拓扑图就可以了。
这里说明一下,如果用scc_cnt来记录每个强连通分量缩点后的点的话。由于在tarjan过程中,每次一遇到dfn==low就会进行缩点,所以如果从点scc_cnt遍历到1的话,该遍历方式一定是在拓扑图中从起点往终点遍历的方式。
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+50;
int n,idx,scc_cnt;
int h[maxn],h1[maxn];
int dfn[maxn],low[maxn],timestamp;
int id[maxn],sz[maxn];
bool in_stk[maxn],flag[maxn];
int f[maxn];
stack<int> stk;
struct Edge{
int v,nxt;
}edge[maxn],edge1[maxn];
void add(int u,int v,int h[],Edge edge[]){
edge[idx].v=v;
edge[idx].nxt=h[u];
h[u]=idx++;
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk.push(u);in_stk[u]=true;
for(int i=h[u];~i;i=edge[i].nxt){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stk[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int y;scc_cnt++;
do{
y=stk.top();
stk.pop();
id[y]=scc_cnt;
sz[scc_cnt]++;
in_stk[y]=false;
}while(y!=u);
}
}
void dfs(int u){
flag[u]=true;
f[u]=sz[u];
for(int i=h1[u];~i;i=edge1[i].nxt){
int v=edge1[i].v;
if(!flag[v]){
dfs(v);
}
f[u]+=f[v];//用f数组记录每个缩点最多能到达的点数
}
}
int main(){
memset(h,-1,sizeof(h));
memset(h1,-1,sizeof(h1));
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
add(i,x,h,edge);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int u=1;u<=n;u++)
for(int i=h[u];~i;i=edge[i].nxt){
int v=edge[i].v;
int x=id[u],y=id[v];
if(x!=y) add(x,y,h1,edge1);
}
for(int i=scc_cnt;i>=1;i--)//此处是遍历拓扑图,直接从scc_cnt遍历到1,而不用初始话拓扑图
if(!flag[i])
dfs(i);
for(int i=1;i<=n;i++){
printf("%d\n",f[id[i]]);
}
return 0;
}