D161 SCC 缩点+拓扑+bitset Tarjan 算法 P4306 [JSOI2010] 连通数

D161 SCC 缩点+拓扑+bitset Tarjan 算法 P4306 [JSOI2010] 连通数_哔哩哔哩_bilibili

 

P4306 [JSOI2010] 连通数 - 洛谷

有向有环图,统计每个点的可达点个数,输出所有点可达点个数之和。

思路

如果是有向无环图,怎样统计可达点的数量?

参考:D150 拓扑排序+bitset P10480 可达性统计 - 董晓 - 博客园

拓扑排序具有向后累计的特点,所以要建反图,这样终点能累计多少,说明它做起点就能到达多少

如果每个点开一个 $int$ 数组来计数的话,会重复统计

解决方法, 每个点开一个 $bitset$ 数组来计数,$bitset$ 可以用或运算去重

例如,$f[1]=(001 11010)_2,f[4]=(001 11000)_2$,说明 $1$ 可达 $1,3,4,5$,$4$ 可达 $3,4,5$

如果是有向有环图,怎样计算可达点个数之和?

先跑 Tarjan 对 SCC 缩点,用 siz[] 记录每个缩点的点数

再对缩点建立 DAG 图,然后,拓扑 DP,用 $bitset$ 计数

最后,枚举任意两个缩点(包括自身),如果可达,缩点之内的点必然可达,累加 $siz[i]*siz[j]$

如图,$ans=3*3+3*2+2*2=19$

image

参考:D15【模板】SCC 缩点 Tarjan 算法 - 董晓 - 博客园

 

// SCC缩点+拓扑排序+bitset O(N)
#include<bits/stdc++.h>
using namespace std;

const int N=2010;
vector<int> e[N],ee[N];
int n,m,in[N],ans;
int dfn[N],low[N],stk[N],top,scc[N],siz[N],cnt;
bitset<N> f[N]; //f[i]表示i这个点的可以抵达点的数量,即二进制中1的个数

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]++;
  }
}
void topo(){
  queue<int> q;
  for(int i=1; i<=cnt; i++) if(!in[i]) q.push(i);
  
  while(!q.empty()){
    int u=q.front(); q.pop();
    f[u][u]=1; //可以到达自己
    for(auto v:ee[u]){
      f[v]|=f[u]; //非重复统计用并集运算
      if(--in[v]==0) q.push(v);
    }
  }
}
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
    string s; cin>>s;
    for(int j=0;j<=s.size();j++) 
      if(s[j]=='1') e[i].push_back(j+1); //连边
  }
  
  for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); //SCC缩点
  
  for(int i=1;i<=n;i++)for(int j:e[i]) //枚举原始点的临接点
    if(scc[j]!=scc[i]) ee[scc[j]].push_back(scc[i]),in[scc[i]]++;
        
  topo(); //对缩点拓扑DP
  
  for(int i=1;i<=cnt;i++)for(int j=1;j<=cnt;j++) //任意两个缩点(包括自身)
    if(f[i][j]) ans+=siz[i]*siz[j]; //如果可达,缩点之内的点必然可达
  cout<<ans;
}

 

posted @ 2026-05-20 23:18  董晓  阅读(43)  评论(0)    收藏  举报