P3387 【模板】缩点

      ~~~~~      P3387 【模板】缩点       ~~~~~      总题单链接

缩点即相关定义

      ~~~~~      强连通分量:原图一个极大子图使得从这个子图中的任意一个点出发都可以到达这个子图的任意一个点。

      ~~~~~      而缩点,就是找强连通分量的过程。

为什么要缩点

      ~~~~~      将有环图变为无环图,然后就可以用一些在环上比较难用的东西,比如 D P DP DP

怎么找到每个点对应的强连通分量

      ~~~~~       d f s dfs dfs序 访问图上的每个点,每个点只访问一遍,每个点第一次访问的时候加入到栈里。

      ~~~~~      先来看看时间戳的定义,若一个点的时间戳为 x x x,那它就是在 d f s dfs dfs 时第 x x x 个被访问到的点。

      ~~~~~      对于图上的每个点,记录 d f n [ i ] dfn[i] dfn[i] 表示 点 i i i 的时间戳, l o w [ i ] low[i] low[i] 表示 点 i i i 在不走进入点 i i i 的点的情况下能走到的最早时间戳。举个栗子,在 d f s dfs dfs 的时候是从 u u u 走到 v v v 的,那 l o w [ v ] low[v] low[v] 就是 v v v 在不仅过过 u u u 的情况下能走到的最早时间戳。

      ~~~~~      若在离开点 u u u 时, l o w [ v ] = d f n [ u ] low[v]=dfn[u] low[v]=dfn[u] 则当前还在栈内的点就是一个强连通分量。为什么?因为 l o w [ v ] = d f n [ u ] low[v]=dfn[u] low[v]=dfn[u] 说明从当前栈内点出发在不经过 u u u 的情况下到不了比 u u u 时间戳更小的点,所以这些点是一个强连通分量。对这句话理解有问题的同学可以再看一看强连通分量的定义。

怎么缩点

      ~~~~~      遍历每条边,如果这条边的两个端点在两个强连通分量,就把这两个强连通分量连边。这样可能会有重边,可以在连边前判断一下。

模板题代码

      ~~~~~      缩点值后就用 d p dp dp 来统计答案。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll dfn[10005],low[10005];
ll pos[10005],siz[10005];
ll stk[10005],init[10005];
vector<ll>eg[10005],ng[10005];
ll n,m,a[10005],top,tot,cnt,dp[1005];

void Tarjan(ll p){
	init[p]=1;
	stk[++top]=p;
	dfn[p]=low[p]=++tot;
	for(ll v:eg[p]){
		if(!dfn[v]){
			Tarjan(v);
			low[p]=min(low[p],low[v]);
		}
		else if(init[v])low[p]=min(low[p],dfn[v]);
	}
	if(low[p]==dfn[p]){
		cnt++;
		while(top){
			ll v=stk[top--];
			init[v]=0;
			pos[v]=cnt;
			siz[cnt]+=a[v];
			if(v==p)break;
		}
	}
}

signed main(){
	ios::sync_with_stdio(false);
	
	cin>>n>>m;
	for(ll i=1;i<=n;i++)cin>>a[i];
	while(m--){
		ll x,y;cin>>x>>y;
		eg[x].push_back(y);
	}
	
	for(ll i=1;i<=n;i++)
		if(!dfn[i])Tarjan(i);
		
	for(ll u=1;u<=n;u++)
		for(ll v:eg[u])
			if(pos[u]!=pos[v])
				ng[pos[u]].push_back(pos[v]);
		
	ll ans=0;
	for(ll i=cnt;i>=1;i--){
		dp[i]+=siz[i];
		ans=max(ans,dp[i]);
		for(ll v:ng[i])
			dp[v]=max(dp[v],dp[i]);
	}
	cout<<ans<<endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值