Tarjan小结-缩点

和lca一样一直忘写了...

引子

仔细看题可以得知,如果将传递关系建有向图,那么要输出图中最小环的大小。我的理解中最裸的tarjan用于判环

int tim,top,tot;
int dfn[MAXN],low[MAXN],flag[MAXN],stac[MAXN],scc[MAXN],cnt[MAXN];
void tarjan(int u){
	dfn[u]=low[u]=++tim;
	stac[++top]=u,flag[u]=1;
	for(int i=0;i<edge[u].size();i++){
		int v=edge[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(flag[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		scc[u]=++tot;
		flag[u]=0;
		cnt[tot]++;
		while(stac[top]!=u){
			cnt[tot]++;
			scc[stac[top]]=tot;
			flag[stac[top]]=0;
			top--;
		}
		--top;
	}
}

...

for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);

本题中,tarjan判scc所要用到的变量是时间tim,栈顶位置top,scc编号tot。

以及前文提到的dfn[],low[],flag[],和模拟的栈stac[],以及存储点i所属scc的scc[i],第i个scc的大小cnt[i]

普遍地,scc[i]常用于将缩点后的图以每个scc作为点构造新图 例题

注意到,当前打算劫掠的atm与这个atm所属的scc都是可以劫掠的,所以完全可以把每个scc的钱数集合起来作为新图里一个点的钱数,跑一边单源多汇即可

#include<bits/stdc++.h>
#define MAXN 500001
using namespace std;
int n,m,p,s,v[MAXN],e[MAXN],val[MAXN],ans;
vector<int>edge[MAXN];
vector<int>nedge[MAXN];
int tim,tot,top;
int dfn[MAXN],low[MAXN],flag[MAXN],stac[MAXN],scc[MAXN];
int ind[MAXN],tps[MAXN],mxd[MAXN];
void tarjan(int u){
	dfn[u]=low[u]=++tim;
	flag[u]=1;stac[++top]=u;
	for(int i=0;i<edge[u].size();i++){
		int v=edge[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(flag[v])low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u]){
		scc[u]=++tot;
		flag[u]=0;
		val[tot]=v[u];
		while(stac[top]!=u){
			scc[stac[top]]=tot;
			val[tot]+=v[stac[top]];//在处理scc的时候顺带将新点i的钱数val[i]处理出来
			flag[stac[top]]=0;
			--top;
		}
		top--;
	}
}
void dfs(int u,int w){//远离spfa
	for(int i=0;i<nedge[u].size();i++){
		int v=nedge[u][i];
		if(w+val[v]>mxd[v]){
			mxd[v]=w+val[v];
			dfs(v,w+val[v]);
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		edge[u].push_back(v);
	}
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	scanf("%d%d",&s,&p);
	for(int i=1;i<=p;i++)scanf("%d",&e[i]);
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);//板子
	for(int u=1;u<=n;u++){
		for(int i=0;i<edge[u].size();i++){//新点的连边操作
			int v=edge[u][i];
			if(scc[u]!=scc[v]){//如果两个旧点所属的scc不同,说明他俩在新图中也需要连边,直接将他们两个所属的scc头目链接即可
				nedge[scc[u]].push_back(scc[v]);
				ind[scc[v]]++;
			}
		}
	}
//于是得到了一个由scc头目组成的,点权代表着scc钱数和的新图被建好了    
	mxd[scc[s]]=val[scc[s]];//自己研究的mxd写法,mxd[i]表示走到i时的最优答案,广泛地应用于答案贪心策略成立的题目中
    //本题中,如果当前路径走到i的钱数并不是走到i的最优钱数,那么当前路径之后的一切经过的点的钱数都是有更优情况的,即从i最优钱数路径出发可以被全部刷新。
	dfs(scc[s],val[scc[s]]);
	for(int i=1;i<=p;i++)ans=max(ans,mxd[scc[e[i]]]);
	printf("%d",ans);
	return 0;
}

还有在新图里跑dp的情况,不过那是dp的事,在此不提。

posted @ 2024-02-20 19:30  RVm1eL_o6II  阅读(2)  评论(0编辑  收藏  举报