洛谷3916 图的遍历

题目描述

给出\(N\)个点,\(M\)条边的有向图,对于每个点\(v\),求\(A(v)\)表示从点\(v\)出发,能到达的编号最大的点。

输入输出格式

输入

第1行,2个整数\(N\),\(M\)
接下来\(M\)行,每行2个整数\(U_i\),\(V_i\),表示边(\(U_i\),\(V_i\))。点用\(1,2,3\cdots N\)编号。

输出

\(N\)个整数\(\$A(1)\),\(A(2)\),\(\cdots,A(N)\)

样例

输入样例

4 3
1 2
2 4
4 3

输出样例

4 4 3 4

思路

做这道题的方法不少。
在这里我只提一种
就是大法师(\(DFS\))。
可以采用反向建边,从最大的点开始\(DFS\)
我们考虑每次从所剩点中最大的一个点出发,我们暂且称它为\(i\),而凡是\(i\)这个点所能到达的点,可以到达的点最大都是\(i\)
在遍历的时候按\(n\)——>\(1\)的顺序
因为是从大到小遍历,故每个点第一次被碰到的i一定是这个点最大可到达的点
上代码(走起)Music!!!

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int dfn[maxn],low[maxn],onStack[maxn],n,m,sum,res[maxn],cnt,scc[maxn],dp[maxn];
stack<int>s;
vector<int>e[maxn];
vector<int>new_e[maxn];
struct node{
	int x,y;
}edge[maxn];
void tarjan(int u){//求点u的dfn和low值  
	s.push(u);//入栈 
	onStack[u]=true;//在栈中
	dfn[u]=low[u]=++cnt;//初始化 
	for(size_t i=0;i<e[u].size();i++){//枚举u指向的点 
		int next=e[u][i];
		if(dfn[next]==0){//如果没访问过
			tarjan(next);
			low[u]=min(low[u],low[next]);
		}
		else if(onStack[next]==true)//访问过且在栈中,说明next跟u属于同一个scc,如果访问过但不在栈中,说明next属于别的scc 
			low[u]=min(low[u],low[next]);
	}
	if(dfn[u]==low[u]){//关键点  
		sum++;//scc数量加1 
		onStack[u]=false;//u即将出栈 
		scc[u]=sum;//点u所属的scc编号 
		res[sum]=max(res[sum],u);//更新当前scc具有的最大编号 
		while(s.top()!=u){
			int cur=s.top();
			s.pop();
			onStack[cur]=false;//cur出栈
			scc[cur]=sum;//记录cur 所属的scc编号 
			res[sum]=max(res[sum],cur);//更新当前scc具有的最大编号 
		}
		s.pop();//弹出u 
	}
}
inline void dfs(int x){
	if(dp[x]>0)//剪枝 
		return ;
	dp[x]=res[x];//初始化, res[i]第i个强连通分量中的最大编号 
	for(size_t i=0;i<new_e[x].size();i++){//注意是新的 new_e
		int next=new_e[x][i];
		if(dp[next]==0)
			dfs(next);
		dp[x]=max(dp[x],dp[next]);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);//x指向y 
		edge[i].x=u;
		edge[i].y=v;
		e[u].push_back(v);	//x的下一步的点 
	}
	cnt=0;//时间戳初始化 
	for(int i=1;i<=n;i++)
		if(dfn[i]==0)//没搜过 
			tarjan(i);
	for(int i=1;i<=m;i++){
		int xx=scc[edge[i].x];//找出第i条边的点u所属的scc编号 
		int yy=scc[edge[i].y];
		if(xx!=yy)
			new_e[xx].push_back(yy);//强连通分量fx指向的下一个scc是fy 
		
	}
	for(int i=1;i<=sum;i++)//记忆化搜索,sum是scc的总数 
		if(dp[i]==0)//dp[i]记录从第i个scc出发能走到的最大编号 
			dfs(i);	
	for(int i=1;i<=n;i++)//每个点 
		printf("%d ",dp[scc[i]]);
	return 0;
}//Excellent!!!!!
posted @ 2019-06-09 16:34  xzj213  阅读(203)  评论(0编辑  收藏  举报