~~~~~ P3119 [USACO15JAN] Grass Cownoisseur G ~~~~~ 总题单链接
思路
~~~~~ 像这种每个点可以重复经过,但只会统计一次答案的题,可以考虑缩点。
~~~~~ 缩点之后跑一次以 1 1 1 所在的连通块为起点的最长路,再跑一次以 1 1 1 所在的连通块为终点的最长路(通过建返图实现),然后枚举反边即可。
~~~~~ 注意跑最长路不能用 D i j k s t r a Dijkstra Dijkstra,我用的 S P F A SPFA SPFA。
~~~~~ 还有就是考虑不走反边的情况,也就是整个图缩成一个连通块的情况。
代码
#include<bits/stdc++.h>
#define ll long long
#define fir first
#define sec second
using namespace std;
vector<ll>eg[100005];
vector<pair<ll,ll>>ng[100005];
ll n,m,S,dis[2][100005];
ll dfn[100005],low[100005],tot;
ll scc[100005],siz[100005],cnt;
ll stk[100005],init[100005],top;
void Tarjan(ll p){
dfn[p]=low[p]=++tot;
stk[++top]=p;init[p]=1;
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(dfn[p]==low[p]){
cnt++;
while(1){
ll v=stk[top--];
init[v]=0;
scc[v]=cnt;
siz[cnt]++;
if(v==p)break;
}
}
}
ll vis[100005];queue<ll>que;
void SPFA(ll opt){
vis[S]=1;que.push(S);
while(!que.empty()){
ll u=que.front();vis[u]=0;que.pop();
for(pair<ll,ll>it:ng[u]){
ll v=it.fir,w=it.sec;
if(dis[opt][v]<dis[opt][u]+w){
dis[opt][v]=dis[opt][u]+w;
if(!vis[v])que.push(v),vis[v]=1;
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
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);
if(cnt==1){
cout<<n;
return 0;
}
S=scc[1];
for(ll u=1;u<=n;u++){
for(ll v:eg[u]){
if(scc[u]==scc[v])continue;
ng[scc[u]].push_back({scc[v],siz[scc[v]]});
}
}
dis[0][S]=siz[S];SPFA(0);
for(ll i=1;i<=cnt;i++)ng[i].clear();
for(ll u=1;u<=n;u++) {
for(ll v:eg[u]){
if(scc[u]==scc[v])continue;
ng[scc[v]].push_back({scc[u],siz[scc[u]]});
}
}
dis[1][S]=siz[S];SPFA(1);
ll ans=siz[S];
for(ll u=1;u<=cnt;u++){
for(pair<ll,ll>it:ng[u]){
ll v=it.fir;
if(dis[0][u]==0||dis[1][v]==0)continue;
ans=max(ans,dis[0][u]+dis[1][v]);
}
}
cout<<ans-siz[S];
return 0;
}