题目链接:https://www.luogu.com.cn/problem/P3119
首先肯定是要用tarjan缩点的,这样图上就不会有环了,你所需做的就是加一条边的反向边,使这张图能有以 1 为起点 和 终点的环,并且这个环要尽可能大。我在洛谷的题解里看到一个非常巧妙的做法,我们可以将缩完点的图复制一下,形成上下两层相同的图,下层每个点的编号是上层每个点编号+col_id(强连通分量的数量),然后上层对于每条从 v 到 u 的边,加一条从 u 到 v+col_id 的边。例如下图
我们每次走红边,就不可能回到黑边,所以走红边就相当于一次逆行。最后我们只需跑一次以 col[1] (包含1的强连通分量) 为起点的spfa即可,答案就是 1 到 1’ 的距离。
代码如下
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int maxm=3e5+5;
struct node
{
int to,next;
}edge1[maxn],edge2[maxm];
int cnt1,cnt2;
int head1[maxn],head2[maxm];
int stc[maxn],dfn[maxn],low[maxn];
int col[maxn];
int col_num[maxm];
bool vis[maxn];
int top,dfn_num,col_id;
void add1(int x,int y)
{
edge1[++cnt1].next=head1[x];
edge1[cnt1].to=y;
head1[x]=cnt1;
}
void add2(int x,int y)
{
edge2[++cnt2].next=head2[x];
edge2[cnt2].to=y;
//edge2[cnt2].w=c;
head2[x]=cnt2;
}
void tarjan(int u)
{
dfn[u]=low[u]=++dfn_num;
stc[++top]=u;
vis[u]=1;
for(int i=head1[u];i;i=edge1[i].next)
{
int v=edge1[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
vis[u]=false;
col[u]=++col_id;
int len=1;
while(stc[top]!=u)
{
len++;
vis[stc[top]]=false;
col[stc[top--]]=col_id;
}
top--;
col_num[col_id]=len;
}
}
int dis[maxm];
queue<int>q;
bool tag[maxm];
int n,m;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add1(x,y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=col_id;i++)
col_num[i+col_id]=col_num[i];
for(int i=1;i<=n;i++)
for(int j=head1[i];j;j=edge1[j].next)
{
int v=edge1[j].to;
if(col[i]!=col[v])//建新图
{
add2(col[i],col[v]);
add2(col[i]+col_id,col[v]+col_id);
add2(col[v],col[i]+col_id);
}
}
tag[col[1]]=1;
q.push(col[1]);
while(!q.empty())
{
int u=q.front();
for(int i=head2[u];i;i=edge2[i].next)
{
int v=edge2[i].to;
if(dis[v]<dis[u]+col_num[u])
{
dis[v]=dis[u]+col_num[u];
if(!tag[v])
{
tag[v]=1;
q.push(v);
}
}
}
q.pop();
tag[u]=0;
}
printf("%d\n",dis[col[1]+col_id]);
return 0;
}