洛谷 P3119 [USACO15JAN]Grass Cownoisseur G 题解
题目传送门
题目大意:有一个有向图,从 号点出发,可以走一次反向边(“逆行”),当然也可以不走,可以重复经过点。求从 号点出发回到 号点最多能经过几个点。
前置知识:如何用线性算法求一张有向图的强连通分量
显然我们发现,如果 号点能到达,那么显然和 点位于同一个强连通分量内的点都可以到达,那么显然我们需要先缩点。
缩点之后就变成了一张DAG图。由于可以逆行一次,所以我们可以考虑分层图,但是其实不需要用分层图。
我们把所有的点进行标号,分成三类点: 号点, 号点能到达的点,能到达 号点的点。分别记作 号点,I类点,II类点。
显然对于任何一条合法的路径都有三段: 号点->I类点->II类点-> 号点
我们可以使用建反向图+SPFA的方法求出 号点到每个I类点最多能经过几个点,每个II类点到 号点最多能经过几个点。由于只能走一次反向边,所以我们只要枚举每一条反向边从而计算答案。
代码:
#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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· 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工具