D159 SCC 缩点+拓扑 Tarjan 算法 P3119 [USACO15JAN] Grass Cownoisseur G
D159 SCC 缩点+拓扑 Tarjan 算法 P3119 [USACO15JAN] Grass Cownoisseur G_哔哩哔哩_bilibili
P3119 [USACO15JAN] Grass Cownoisseur G - 洛谷
n 个点 m 条有向边的有环图,从 1 号点出发,路径最多翻转一条边,回到 1 号点,求经过的最多点数(点不重复计数)。
思路
先跑 Tarjan 缩点,同时记录每个缩点的点数 siz[]
设 1 号点所在缩点的编号为 s,用 d 数组记录路径上点的数量
对缩点建正图,拓扑求从 s 到各点的最长路 d1[]
对缩点建反图,拓扑求从 s 到各点的最长路 d2[]
注意,s 点可能不是入度为 0 的点,要把所有点 d 值初始化为 0,把 d[s] 初始化为 siz[s],只更新从 s 能到的点的 d 值
样例建图,2,4,7 缩成一个点,缩点图应该重新编号的,我用原始点方便观察

最后,枚举每条边的端点,满足正图能到 y 且 反图能到 x,说明有向边 $(x,y)$ 是可以翻转的,就拼接路径,更新答案
$ans=max\{ \;d1[scc[y]]+d2[scc[x]]-siz[s] \;\}$,因为 1 号点的 $siz$ 算了两次
相关板子:
D15【模板】SCC 缩点 Tarjan 算法 - 董晓 - 博客园
// SCC 缩点 Tarjan 算法 O(N) #include<bits/stdc++.h> using namespace std; const int N=100005; vector<int> e[N],e1[N],e2[N]; int n,m,ans; int dfn[N],low[N],stk[N],top,scc[N],siz[N],cnt; int d1[N],d2[N]; void tarjan(int x){ //SCC缩点 dfn[x]=low[x]=++dfn[0]; stk[++top]=x; for(auto y:e[x]){ if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]); else if(!scc[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]){ ++cnt; while(stk[top+1]!=x) scc[stk[top--]]=cnt,siz[cnt]++; } } int main(){ scanf("%d%d",&n,&m); for(int a,b;m--;) scanf("%d%d",&a,&b),e[a].push_back(b); for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i); //SCC缩点 for(int x=1; x<=n; x++)for(auto y:e[x]){ //枚举原始点的邻接点 int a=scc[x],b=scc[y]; if(a!=b) e1[a].push_back(b); //对缩点建正图 if(a!=b) e2[b].push_back(a); //对缩点建反图 } int s=scc[1]; //1号点做起点 d1[s]=siz[s]; //起点自身可达 for(int x=cnt; x; x--)for(auto y:e1[x]) //枚举缩点的邻接点 if(d1[x]) d1[y]=max(d1[y],d1[x]+siz[y]); //正图从s到各点的最长路d1[] d2[s]=siz[s]; //起点自身可达 for(int x=1; x<=cnt; x++)for(auto y:e2[x]) //枚举缩点的邻接点 if(d2[x]) d2[y]=max(d2[y],d2[x]+siz[y]); //反图从s到各点的最长路d2[] ans=siz[s]; //从s出不去的情况 for(int x=1; x<=n; x++)for(auto y:e[x]){ //枚举原始点的邻接点 if(d1[scc[y]] && d2[scc[x]]) //正图能到y且反图能到x ans=max(ans,d1[scc[y]]+d2[scc[x]]-siz[s]); //拼接时s点算了两次 } printf("%d\n",ans); }
浙公网安备 33010602011771号