从「缩点」到DAG:用Tarjan+Kosaraju搞定洛谷P3387,彻底弄懂有向图强连通分量
在算法竞赛中,图论问题往往考验选手对复杂结构的拆解能力。洛谷P3387作为一道经典模板题,完整呈现了「强连通分量→缩点→DAG动态规划」的技术链条。本文将带你穿透理论迷雾,用两种截然不同的算法视角(Tarjan与Kosaraju)解剖问题本质,最终在缩点后的DAG上实现优雅的动态规划解法。
1. 强连通分量的双重解法
1.1 Tarjan算法的精妙设计
Tarjan算法之所以成为图论领域的瑞士军刀,在于其巧妙的双时间戳机制。让我们通过洛谷P3387的输入样例,观察算法如何逐步标记图中的强连通分量:
cpp复制vector<int> dfn(n+1); // 访问次序标记
vector<int> low(n+1); // 可回溯的最早次序
stack<int> stk; // 递归栈
vector<bool> inStk(n+1);
int timestamp = 1;
void tarjan(int cur) {
dfn[cur] = low[cur] = timestamp++;
stk.push(cur);
inStk[cur] = true;
for(int nex : graph[cur]) {
if(!dfn[nex]) {
tarjan(nex);
low[cur] = min(low[cur], low[nex]);
} else if(inStk[nex]) {
low[cur] = min(low[cur], dfn[nex]);
}
}
if(dfn[cur] == low[cur]) { // 发现SCC
int x;
do {
x = stk.top(); stk.pop();
inStk[x] = false;
scc[x] = cur; // 用cur作为代表元
} while(x != cu
