2 条题解

  • 0
    @ 2026-6-23 13:06:49

    upd 2026/6/24:hack 了 yzc 的代码。

    请注意有向图缩完后其实是个 DAG,你根本回不去。

    所以逆向走其实必须从一个 scc[1] 能到的连通块回到一个能到 scc[1] 的连通块。

    正向 bfs 一遍反向 bfs 一遍确认能到 scc[1] 和 scc[1] 能到的连通块。

    再根据这两个图分别正向拓扑和反向拓扑求权值。 然后遍历每条边看看是不是逆向能回到 1 就行了。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    vector<int>G[N],G2[N],G3[N];
    int tsp,cnt,scc[N],low[N],dfn[N],rd[N],rd1[N],siz[N];
    stack<int>stk;bool instk[N];
    void tarjan(int x)
    {
        low[x]=dfn[x]=++tsp;
        stk.push(x);instk[x]=1;
        for(int y:G[x])
        {
            if(dfn[y]==0)
            {
                tarjan(y);
                low[x]=min(low[x],low[y]);
            }
            else if(instk[y])low[x]=min(low[x],dfn[y]);
        }
        if(low[x]==dfn[x])
        {
            cnt++;
            for(int z=-1;z!=x;)
            {
               z=stk.top();stk.pop();instk[z]=false;
               scc[z]=cnt;siz[cnt]++;
            }
        }
    } 
    int dp[N],dp1[N],v[N],v1[N];
    int main()
    {
    	int n,m;cin>>n>>m;
    	for(int i=1;i<=m;i++)
    	{
    		int x,y;cin>>x>>y;
    		G[x].push_back(y);
    	} 
    	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
    	map<pair<int,int>,int>mp;
    	for(int i=1;i<=n;i++)for(auto j:G[i])
    	{
    		int x=scc[i],y=scc[j];
    		if(!mp[{x,y}]&&x!=y)
    			G2[x].push_back(y),G3[y].push_back(x),mp[{x,y}]=1;
    	}
    	deque<int>q;
    	q.push_back(scc[1]);v[scc[1]]=1;
    	while(!q.empty())
    	{
    		int x=q.front();q.pop_front();
    		for(int y:G2[x])if(!v[y])
    			v[y]=1,q.push_back(y);
    	}
    	q.push_back(scc[1]);v1[scc[1]]=1;
    	while(!q.empty())
    	{
    		int x=q.front();q.pop_front();
    		for(int y:G3[x])if(!v1[y])
    			v1[y]=1,q.push_back(y);
    	}
    	for(int i=1;i<=cnt;i++)
    	{
    		for(int j:G2[i])
    		{
    			if(v1[i]&&v1[j])
    				rd1[i]++;
    			if(v[i]&&v[j])
    				rd[j]++;
    		}
    	}
    	q.push_back(scc[1]);
    	while(!q.empty())
    	{
    		int x=q.front();q.pop_front();
    		dp[x]+=siz[x];
    		for(int y:G2[x])
    		{
    			dp[y]=max(dp[y],dp[x]);
    			rd[y]--;if(rd[y]==0)q.push_back(y);
    		}
    	}
    	q.push_back(scc[1]);
    	while(!q.empty())
    	{
    		int x=q.front();q.pop_front();
    		dp1[x]+=siz[x];
    		for(int y:G3[x])
    		{
    			dp1[y]=max(dp1[y],dp1[x]);
    			rd1[y]--;if(rd1[y]==0)q.push_back(y);
    		}
    	}
    	int ans=dp[scc[1]];
    	for(int i=1;i<=cnt;i++)if(v1[i])
    		for(int j:G2[i])if(v[j])
    			ans=max(ans,dp1[i]+dp[j]-siz[scc[1]]);
    	cout<<ans;
    	return 0;
    }
    • 0
      @ 2026-6-13 21:33:16

      // SCC 缩点 Tarjan 算法 O(N)
      #include<bits/stdc++.h>
      using namespace std;
      
      const int N=100005;
      vector<int> e[N],e1[N],e2[N];
      int n,m,ans;
      int dfn[N],low[N],stk[N],top,scc[N],siz[N],cnt;
      int d1[N],d2[N];
      
      void tarjan(int x){ //SCC缩点
        dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
        for(auto y:e[x]){
          if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
          else if(!scc[y]) low[x]=min(low[x],dfn[y]); 
        }
      
        if(dfn[x]==low[x]){
          ++cnt;
          while(stk[top+1]!=x) scc[stk[top--]]=cnt,siz[cnt]++;
        }
      }
      int main(){
        scanf("%d%d",&n,&m);
        for(int a,b;m--;) scanf("%d%d",&a,&b),e[a].push_back(b);
        
        for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i); //SCC缩点
      
        for(int x=1; x<=n; x++)for(auto y:e[x]){ //枚举原始点的邻接点
          int a=scc[x],b=scc[y];
          if(a!=b) e1[a].push_back(b); //对缩点建正图
          if(a!=b) e2[b].push_back(a); //对缩点建反图
        }
        
        int s=scc[1]; //1号点做起点
        d1[s]=siz[s]; //起点自身可达
        for(int x=cnt; x; x--)for(auto y:e1[x]) //枚举缩点的邻接点
          if(d1[x]) d1[y]=max(d1[y],d1[x]+siz[y]); //正图从s到各点的最长路d1[]
        
        d2[s]=siz[s]; //起点自身可达
        for(int x=1; x<=cnt; x++)for(auto y:e2[x]) //枚举缩点的邻接点
          if(d2[x]) d2[y]=max(d2[y],d2[x]+siz[y]); //反图从s到各点的最长路d2[]
        
        ans=siz[s]; //从s出不去的情况
        for(int x=1; x<=n; x++)for(auto y:e[x]){ //枚举原始点的邻接点
          if(d1[scc[y]] && d2[scc[x]]) //正图能到y且反图能到x
            ans=max(ans,d1[scc[y]]+d2[scc[x]]-siz[s]); //拼接时s点算了两次
        }
        printf("%d\n",ans);
      }
      
      • 1

      D159 SCC 缩点+拓扑 [USACO15JAN] Grass Cownoisseur G

      信息

      ID
      6731
      时间
      1000ms
      内存
      512MiB
      难度
      8
      标签
      递交数
      28
      已通过
      5
      上传者