D161 SCC 缩点+拓扑+bitset Tarjan 算法 P4306 [JSOI2010] 连通数
D161 SCC 缩点+拓扑+bitset Tarjan 算法 P4306 [JSOI2010] 连通数_哔哩哔哩_bilibili
有向有环图,统计每个点的可达点个数,输出所有点可达点个数之和。
思路
如果是有向无环图,怎样统计可达点的数量?
参考: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$

参考: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; }
浙公网安备 33010602011771号