D160 SCC 缩点+拓扑 Tarjan 算法 P2272 [ZJOI2007] 最大半连通子图
D160 SCC 缩点+拓扑 Tarjan 算法 P2272 [ZJOI2007] 最大半连通子图_哔哩哔哩_bilibili
给了个有向有环图,选出一些点,满足两两至少单向可达,求选出点数的最大值,以及最大值的方案数
思路
Tarjan 缩点后,变成 DAG 图,题目就是求点数最多的最长路,以及最长路的条数
按缩点编号递减的顺序就是拓扑序,对拓扑序 DP,求最长路 $d[]$
(1)如果最长路更长,那么更新最长路 $d[y]=d[x]+siz[y]$,同时,继承方案数 $f[y]=f[x]$
(2)如果最长路相等,那么只更新方案数 $f[y]=f[y]+f[x]$
参考:D100 最短路条数 Dijkstra 算法 P1608 路径统计 - 董晓 - 博客园
注意:缩点后会在缩点之间出现重边,重边对最长路的转移不会产生影响,但是对方案数会重复统计
所以对缩点重新建图时,要用哈希表判重,去掉缩点之间的重边
如图,4 个点满足两两之间至少单向可达,缩点后变成两个点,如果不去掉缩点之间的重边,方案数会统计成 2

最后,枚举缩点,累加最长路的方案数
相关板子:
D15【模板】SCC 缩点 Tarjan 算法 - 董晓 - 博客园
// SCC 缩点 Tarjan 算法 O(N) #include<bits/stdc++.h> #define ll long long using namespace std; const int N=100010; vector<int> e[N],ee[N]; int n,m,mod; int dfn[N],low[N],stk[N],top,scc[N],siz[N],cnt; int d[N],f[N],mx,sum; 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%d",&n,&m,&mod); 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缩点 unordered_set<ll> S; //哈希表判重边 for(int x=1; x<=n; x++)for(auto y:e[x]){ //枚举每个点的邻接点 int a=scc[x],b=scc[y]; ll hash=a*1000000ll+b; //端点的哈希值 if(a!=b && !S.count(hash)){ //不在一个SCC且边(a,b)没被加过 ee[a].push_back(b); //缩点之间连边 S.insert(hash); //记录哈希值 } } for(int x=cnt; x; x--){ //枚举缩点 if(d[x]==0){ //拓扑图起点 d[x]=siz[x]; mx=max(mx,d[x]); f[x]=1; } for(auto y:ee[x]){ //枚举邻接点 if(d[y]<d[x]+siz[y]){ d[y]=d[x]+siz[y]; //更新最长路 mx=max(mx,d[y]); f[y]=f[x]; //继承方案数 } else if(d[y]==d[x]+siz[y]){ f[y]=(f[y]+f[x])%mod; //更新方案数 } } } for(int i=1; i<=cnt; i++) //枚举缩点 if(d[i]==mx) sum=(sum+f[i])%mod; //累加最长路的方案数 printf("%d\n%d\n",mx,sum); }
浙公网安备 33010602011771号