D15【模板】SCC 缩点 Tarjan 算法

D15【模板】SCC 缩点 Tarjan 算法_哔哩哔哩_bilibili

 

参考:D14【模板】强连通分量 Tarjan 算法 - 董晓 - 博客园

图的问题注意:有向无向?有环无环?连通不连通?正权负权?

对有向有环图,把每个强连通分量缩成一个点

这张图会变成一个 DAG,可以进行拓扑排序以及更多其他操作

 

题目1: P2812 校园网络【[USACO]Network of Schools加强版】 - 洛谷

1. SCC缩点

2. 统计缩点的入度、出度

3. 构造答案

// SCC 缩点 Tarjan 算法 O(N)
#include<bits/stdc++.h>
using namespace std;

const int N=10010;
int n,m,a,b;
vector<int> e[N]; 
int dfn[N],low[N],stk[N],top,scc[N],cnt;
int in[N],out[N]; //SCC的入度,出度

void tarjan(int x){
  dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
  for(int 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;
  }
}
int main(){
  cin>>n;
  for(int i=1,a; i<=n; i++)
    while(cin>>a,a) e[i].push_back(a);
    
  for(int i=1; i<=n; i++)if(!dfn[i]) tarjan(i);
    
  for(int x=1; x<=n; x++)for(int y:e[x])
    if(scc[x]!=scc[y]) in[scc[y]]++,out[scc[x]]++;
    
  int a=0,b=0;
  for(int i=1; i<=cnt; i++){
    if(!in[i]) a++;
    if(!out[i]) b++;
  }
  printf("%d\n",a);
  printf("%d",cnt==1?0:max(a,b));
}

 

题目2: P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G - 洛谷

1. 缩点

2. 统计缩点的出度

3. 如果出度为 0 的缩点个数 $>1$,那么全明星牛数 $=0$

 如果出度为 0 的缩点个数 $=1$,那么全明星牛数 $=$ 出度为 0 的缩点包含的点数

#include<bits/stdc++.h>
using namespace std;

const int N=10010;
int n,m,a,b;
vector<int> e[N]; 
int dfn[N],low[N],stk[N],top,scc[N],siz[N],cnt;
int out[N]; //SCC的出度

void tarjan(int x){
  dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
  for(int 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(){
  cin>>n>>m;
  while(m--) cin>>a>>b,e[a].push_back(b);

  for(int i=1; i<=n; i++)if(!dfn[i]) tarjan(i);

  for(int x=1; x<=n; x++)for(int y : e[x])
    if(scc[x]!=scc[y]) ++out[scc[x]];
    
  int sum=0,zeros=0;
  for(int i=1; i<=cnt; i++)if(out[i]==0){
    sum=siz[i];
    ++zeros; //出度为0的SCC的个数
  }
  if(zeros>1) sum=0;
  cout<<sum<<endl;
}

 

题目3: P3387 【模板】缩点 - 洛谷

1. 缩点

2. 对缩点建拓扑图(注意编号逆序)

3. 对拓扑图求最长路

#include<bits/stdc++.h>
using namespace std;

const int N=100010;
vector<int> e[N],ne[N];
int n,m;
int dfn[N],low[N],stk[N],top,scc[N],cnt;
int w[N],nw[N],d[N];

void tarjan(int x){
  dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
  for(int 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;
  }
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>w[i];
  for(int i=1,a,b;i<=m;i++) cin>>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++){ //枚举原始点
    nw[scc[x]]+=w[x]; //累加缩点的点权
    for(int y:e[x])if(scc[x]!=scc[y]) //不在同一个缩点
      ne[scc[x]].push_back(scc[y]); //缩点之间连边
  }
  
  for(int x=cnt;x;x--){ //枚举缩点(缩点编号是拓扑逆序的)
    if(d[x]==0) d[x]=nw[x]; //起点
    for(int y:ne[x]) d[y]=max(d[y],d[x]+nw[y]); //更新最长路
  } 

  cout<<*max_element(d+1,d+cnt+1);
}

 

posted @ 2022-05-28 13:29  董晓  阅读(2152)  评论(0)    收藏  举报