D158 SCC 缩点 Tarjan 算法 P1653 [USACO04DEC] Cow Ski Area G

D158 SCC 缩点 Tarjan 算法 P1653 [USACO04DEC] Cow Ski Area G_哔哩哔哩_bilibili

 

P1653 [USACO04DEC] Cow Ski Area G - 洛谷

m 列 n 行的格子图,相邻格子可走,但不能由低到高。问至少连多少条边,可使任意两个格子互达。

思路

建图:把格点映射为点,从高向低连有向边,等高的连双向边

跑 Tarjan 算法,把有向图中的强连通分量均缩点,变成有向无环图(DAG)

统计入度为 0 的缩点的个数 $s_1$,出度为 0 的缩点的个数 $s_2$

要使 DAG 图变成一个强连通图,至少连边条数为 $max(s_1,s_2)$

因为消除 $s_1$ 个入度为 0 的点,需外加 $s_1$ 条边连接,同理,消除 $s_2$ 个出度为 0 的点,需外加 $s_2$ 条边连接

如图,样例缩点后是 6 个点,3 个入度为 0 的点,两个出度为 0 的点,循环连接即构成环

image       

相关板子:

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

 

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

const int N=250005,M=4*N;
int to[M],ne[M],h[N],idx;
void add(int a,int b){
  to[++idx]=b,ne[idx]=h[a],h[a]=idx;
}
int n,m,a[505][505];
int dfn[N],low[N],stk[N],top,scc[N],cnt;
int in[N],out[N],s1,s2;

int get(int i,int j){return (i-1)*m+j;} //格点的编号
void ade(int i,int j){
  if(i>1){
    if(a[i][j]>=a[i-1][j]) add(get(i,j),get(i-1,j)); //向上连边
    if(a[i][j]<=a[i-1][j]) add(get(i-1,j),get(i,j)); //向下连边
  }
  if(j>1){
    if(a[i][j]>=a[i][j-1]) add(get(i,j),get(i,j-1)); //向左连边
    if(a[i][j]<=a[i][j-1]) add(get(i,j-1),get(i,j)); //向右连边
  }
}

void tarjan(int x){ //SCC缩点
  dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
  for(int i=h[x];i;i=ne[i]){
    int y=to[i];
    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(){
  scanf("%d%d",&m,&n); //m列n行
  for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
    scanf("%d",&a[i][j]),ade(i,j); //连边
    
  for(int i=1;i<=n*m;++i) if(!dfn[i]) tarjan(i); 
  
  for(int i=1;i<=n*m;++i)for(int j=h[i];j;j=ne[j]) //枚举每个格点的出边
    if(scc[i]!=scc[to[j]]) ++in[scc[to[j]]],++out[scc[i]];
    
  for(int i=1;i<=cnt;++i){
    if(!in[i]) ++s1;  //入度为0的缩点个数
    if(!out[i]) ++s2; //出度为0的缩点个数
  }
  printf("%d",cnt==1?0:max(s1,s2));
  return 0;
}

 

posted @ 2026-05-16 23:28  董晓  阅读(54)  评论(0)    收藏  举报