【洛谷3119】[USACO15JAN] Grass Cownoisseur G(Tarjan+SPFA)
- 给定一张\(n\)个点\(m\)条边的有向图。
- 从\(1\)号点出发走回\(1\)号点,可以走至多一次反向边,问最多能经过多少节点。
- \(n,m\le10^5\)
\(Tarjan\)
不考虑可以走反向边这一条件,那么答案就是\(1\)号点所在强连通分量的点数。
众所周知强连通分量可以用\(Tarjan\)。
其实也正是为了复习\(Tarjan\)我才会来打这道题的。。。
总而言之,我们先对整张图跑遍\(Tarjan\)缩一次点。
走反向边
考虑走反向边,必然是从\(1\)号点所在强连通分量向下走到某一个点,然后走反向边回到\(1\)号点之前的一个点,再走回到\(1\)号点所在的强连通分量。
我们枚举反向边\((x,y)\),令\(u_i\)为\(1\)号点到其后面每个点的最长路,\(v_i\)为\(1\)号点到其前面每个点的最长路,那么答案就是最大的\(Sz_{c_1}+u_x+v_y\)。
想了半天不太清楚怎么从\(DAG\)中间开始拓扑。。。
于是只好暴力地写了个\(SPFA\)求最长路了。
代码:\(O(SPFA)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m;
struct Graph
{
int ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
I int operator [] (CI x) Con {return lnk[x];}
I edge operator () (CI x) Con {return e[x];}
}G,nG,iG;
namespace Tarjan//Tarjan缩点
{
int d,dfn[N+5],low[N+5],T,S[N+5],IS[N+5],cnt,c[N+5],Sz[N+5];
I void dfs(CI x)
{
dfn[x]=low[x]=++d,IS[S[++T]=x]=1;for(RI i=G[x];i;i=G(i).nxt) dfn[G(i).to]?
IS[G(i).to]&&Gmin(low[x],dfn[G(i).to]):(dfs(G(i).to),Gmin(low[x],low[G(i).to]));
if(dfn[x]==low[x]) {++cnt;W(++Sz[c[S[T]]=cnt],IS[S[T]]=0,S[T--]^x);}
}
}
namespace SPFA//SPFA求最长路
{
int IQ[N+5];queue<int> q;
int u[N+5],v[N+5];I void Work(Con Graph& G,int* dis,CI x)
{
RI i,k,y,t;dis[x]=0,q.push(x);W(!q.empty())
for(i=G[k=q.front()],q.pop(),IQ[k]=0;i;i=G(i).nxt) y=G(i).to,
dis[y]<(t=dis[k]+Tarjan::Sz[y])&&(dis[y]=t,!IQ[y]&&(q.push(y),IQ[y]=1));
}
}
int main()
{
using namespace Tarjan;using namespace SPFA;
RI i,j,x,y;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),G.add(x,y);
for(i=1;i<=n;++i) !dfn[i]&&(dfs(i),0);for(i=1;i<=n;++i) for(j=G[i];//建新图
j;j=G(j).nxt) c[i]^c[G(j).to]&&(nG.add(c[i],c[G(j).to]),iG.add(c[G(j).to],c[i]),0);
memset(u,-1,sizeof(u)),memset(v,-1,sizeof(v)),Work(nG,u,c[1]),Work(iG,v,c[1]);//跑两侧最长路
RI t=Sz[c[1]];for(i=1;i<=cnt;++i) if(~u[i])
for(j=iG[i];j;j=iG(j).nxt) ~v[iG(j).to]&&Gmax(t,Sz[c[1]]+u[i]+v[iG(j).to]);//枚举反向边统计答案
return printf("%d\n",t),0;
}
待到再迷茫时回头望,所有脚印会发出光芒