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;
}
### 原理 Tarjan是针对有向图的操作。强连通分量指里面的两两之间互相可达,当一道题中互相可达的有某种等价联系(一个强连通分量等价于一个的作用)时,就可以进行。Tarjan算法通过DFS序和low数组,利用回溯信息判断图的连通性结构。对于有向图求强连通分量需借助栈。后图会变成一个有向无环图(DAG),因为强连通分量被合并成了一个,消除了环的存在 [^2][^3]。 ### 实现 虽然引用未给出具体代码实现,但Tarjan实现的大致步骤如下: 1. 初始化`dfn`数组记录DFS序,`low`数组记录能追溯到的最早的节的DFS序,栈用于辅助寻找强连通分量。 2. 从一个未访问过的节开始进行深度优先搜索(DFS)。 3. 在DFS过程中,更新`dfn`和`low`数组。 4. 当一个节的`dfn`和`low`相等时,说明找到了一个强连通分量,将栈中元素弹出直到该节,这些元素构成一个强连通分量。 5. 把每个强连通分量成一个,重新构建图。 ### 应用 - **提高效率**:可以把一些等价的的操作一起做,避免对等价进行重复操作,加快计算速度 [^2]。 - **处理DAG上的算法**:之后的图必然是一个DAG,可以在上面进行拓扑排序、动态规划(dp)等操作 [^2]。 - **解决特定类型题目**:如在一些图论题目中,是重要的解题步骤,像模板题P3387模板 [^4]。 ```python # 以下是一个简单的伪代码示例来展示Tarjan的大致框架 def tarjan(u): global index dfn[u] = low[u] = index index += 1 stack.append(u) in_stack[u] = True for v in graph[u]: if dfn[v] == 0: tarjan(v) low[u] = min(low[u], low[v]) elif in_stack[v]: low[u] = min(low[u], dfn[v]) if dfn[u] == low[u]: scc = [] while True: v = stack.pop() in_stack[v] = False scc.append(v) if v == u: break # 这里可以进行操作,例如标记每个所属的强连通分量 for node in scc: scc_id[node] = len(scc_list) scc_list.append(scc) # 初始化 n = 10 # 节数量 graph = [[] for _ in range(n)] dfn = [0] * n low = [0] * n stack = [] in_stack = [False] * n index = 1 scc_id = [-1] * n scc_list = [] # 假设已经构建好图graph # 对每个未访问的节进行Tarjan算法 for i in range(n): if dfn[i] == 0: tarjan(i) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值