洛谷 P3119 [USACO15JAN]Grass Cownoisseur G 题解

题目传送门
题目大意:有一个有向图,从 1 号点出发,可以走一次反向边(“逆行”),当然也可以不走,可以重复经过点。求从 1 号点出发回到 1 号点最多能经过几个点。

前置知识:如何用线性算法求一张有向图的强连通分量

显然我们发现,如果 x 号点能到达,那么显然和 x 点位于同一个强连通分量内的点都可以到达,那么显然我们需要先缩点。
缩点之后就变成了一张DAG图。由于可以逆行一次,所以我们可以考虑分层图,但是其实不需要用分层图。
我们把所有的点进行标号,分成三类点: 1 号点, 1 号点能到达的点,能到达 1 号点的点。分别记作 1 号点,I类点,II类点。
显然对于任何一条合法的路径都有三段: 1 号点->I类点->II类点-> 1 号点
我们可以使用建反向图+SPFA的方法求出 1 号点到每个I类点最多能经过几个点,每个II类点到 1 号点最多能经过几个点。由于只能走一次反向边,所以我们只要枚举每一条反向边从而计算答案。

代码:

#include<queue>
#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define maxn 100039
#define maxm 200039
using namespace std;
//#define debug
typedef int Type;
inline Type read(){
Type sum=0;
int flag=0;
char c=getchar();
while((c<'0'||c>'9')&&c!='-') c=getchar();
if(c=='-') c=getchar(),flag=1;
while('0'<=c&&c<='9'){
sum=(sum<<1)+(sum<<3)+(c^48);
c=getchar();
}
if(flag) return -sum;
return sum;
}
int n,m,u,v;
int head[maxn],nex[maxm],to[maxm],kkk;
#define add(x,y) to[++kkk]=y;\
nex[kkk]=head[x];\
head[x]=kkk;
struct Stack{
int data[maxn],_top;
inline int top(){ return data[_top]; }
inline void push(int x){ data[++_top]=x; return; }
inline void pop(){ _top--; return; }
inline bool empty(){ return _top<=0; }
}s;
int dfn[maxn],low[maxn],cnt,scc[maxn],siz[maxn],num,vis[maxn];
void dfs(int x){
s.push(x); vis[x]=1;
low[x]=dfn[x]=++cnt;
for(int i=head[x];i;i=nex[i]){
if(!dfn[to[i]]) dfs(to[i]);
if(vis[to[i]]) low[x]=min(low[x],low[to[i]]);
}
if(low[x]==dfn[x]){
vis[x]=0; scc[x]=++num;
while(s.top()!=x){
vis[s.top()]=0;
scc[s.top()]=num;
s.pop();
}
s.pop();
}
return;
}
void Tarjan(){
for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i);
return;
}
struct Graph{
int _head[maxn],_nex[maxm],_to[maxm],_kkk;
inline void _add(int x,int y){
_to[++_kkk]=y;
_nex[_kkk]=_head[x];
_head[x]=_kkk;
return;
}
}a,b;
int f1[maxn],f2[maxn],scc1;
//flag[i]=1 1->i flag[i]=2 i->1
queue<int> q,E;
void SPFA(Graph a,int f[]){
memset(vis,0,sizeof(vis));
q=E; q.push(scc[1]); f[scc[1]]=siz[scc[1]];
while(!q.empty()){
int cur=q.front(); q.pop();
for(int i=a._head[cur];i;i=a._nex[i]){
if(f[cur]+siz[a._to[i]] > f[a._to[i]]){
f[a._to[i]]=max(f[a._to[i]],f[cur]+siz[a._to[i]]);
if(!vis[a._to[i]]) q.push(a._to[i]);
vis[a._to[i]]=1;
}
}
vis[cur]=0;
}
return;
}
int main(){
#ifndef ONLINE_JUDGE
//freopen("P3119_3.in","r",stdin);
//freopen("1.out","w",stdout);
#endif
n=read(); m=read();
for(int i=1;i<=m;i++){
u=read(); v=read();
add(u,v);
}
Tarjan();
for(int i=1;i<=n;i++) siz[scc[i]]++;
if(siz[scc[1]]==n){
printf("%d",n);
return 0;
}
for(int i=1;i<=n;i++){
for(int j=head[i];j;j=nex[j])
if(scc[i] != scc[to[j]]){
a._add(scc[i],scc[to[j]]);
b._add(scc[to[j]],scc[i]);
}
}
scc1=scc[1]; SPFA(a,f1); SPFA(b,f2);
int ans=siz[scc[1]];
for(int i=1;i<=num;i++)
if(f1[i])
for(int j=b._head[i];j;j=b._nex[j])
if(f2[b._to[j]])
ans=max(ans,f1[i]+f2[b._to[j]]-siz[scc[1]]);
printf("%d",ans);
return 0;
}
posted @   jiangtaizhe001  阅读(84)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示