tarjan求强连通分量以及一些应用.

推荐一下神仙的博客

t a r j a n tarjan tarjan拖到这个时候才来学真的是我菜.

这次一定要简单了说.

/*
tarjan 可以用来求强连通分量.
可以把同一个强连通分量中的点全都标上同一个数字.
我们接下来就看一看怎么写的.
*/

首先我们思考一下,对整张图进行深度优先搜索,用 d f n dfn dfn数组标记每个点是第几个搜索到的.
那么有概率出现一个非常奇妙的事情:
一个点dfs过去的时候忽地指向了它自己!
这时候从这个点 d f s dfs dfs刚才的一圈当然就是个强连通子图了.
然而重要的是,这个不能算是强连通分量啊,要极大的才算.
那怎么办啊?
我们骚一个数组 l o w low low,代表所有和某个点构成强连通分量的点中最小的 d f n dfn dfn.
这样只要一个点没有一个儿子(即比它后面 d f s dfs dfs到的点)指向比它前面 d f s dfs dfs到的点(当然这些点都得在同一个强连通分量里),这个点的 d f n dfn dfn就等于 l o w low low.那么这个问题就解决了.
现在还剩下怎么判断一个节点连向的节点是和它在同一个强连通分量里的.
开栈记录.
好了,游戏结束.

好玩的开始了

首先你需要准备面粉,鸡蛋,烤箱,奶油 d f n , l o w , v i s , s t a c k ( 栈 ) dfn,low,vis,stack(栈) dfn,low,vis,stack()这4个数组(栈).
接下来你需要燃起来.

fuko dfn,low,vis,col;
stack<int> lxy;//栈随便取一个名字
int cnt,ans;
void tarjan(int u){
dfn[u]=low[u]=++cnt;//首先给u分配一个编号
vis[u]=1,lxy.push(u);//标明u在栈里并加入栈.
for (int v:lj[u]){
  if (!dfn[v]||vis[v]){//如果v没有编号(没有dfs过)或者已经在此强连通子图里.
    if (!dfn[v]) tarjan(v);//如果v没有编号,去跑它.
    low[u]=min(low[u],low[v]);//更新low.
    }
  }
if (dfn[u]==low[u]){//它自己是编号最小的点.
  col[u]=++ans,vis[u]=0;//染色,退栈
  for (;lxy.top()^u;lxy.pop()){//这个强连通分量都退栈
    col[lxy.top()]=ans;
    vis[lxy.top()]=0;
    }lxy.pop();//最后把u退栈.
  }
}

那么强连通分量就搞定了.
例题什么的都在上面给的博客里.

缩点

求了强连通分量,就可以把每个强连通分量缩成一个非常大的点,点权可以自定. 这样原来的图转化为一个DAG,做题从未如此轻松.
我来写一下上面的博客没有给出的洛谷缩点模板(当时那个博客写的时候没有这个模板).

/*
首先缩成的非常大的点的那个权值非常容易知道,就是包含的所有点的权值之和.
我们把原来的边保存一下,缩完点之后把不在同一个强连通分量中的边重新连上.
(卧槽开了80000的边,直接re)
然后开一个DAGdp,每个点的答案就是它的权值加上所有它指向的边的最大权值.
(反正DAG,直接爆搜就可以了.)
然后就过去了.
*/
#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rec register char
#define rel register ll
#define gc getchar
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
inline int read(){
  int x=0,f=1;char c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return f?x:-x;
  }
template <typename mitsuha>
inline bool read(mitsuha &x){
  x=0;int f=1;char c=gc();
  for (;!isdigit(c)&&~c;c=gc()) f^=c=='-';
  if (!~c) return 0;
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return x=f?x:-x,1;
  }
template <typename mitsuha>
inline int write(mitsuha x){
  if (!x) return 0&pc(48);
  if (x<0) x=-x,pc('-');
  int bit[20],i,p=0;
  for (;x;x/=10) bit[++p]=x%10;
  for (i=p;i;--i) pc(bit[i]+48);
  return 0;
  }
inline char fuhao(){
  char c=gc();
  for (;isspace(c);c=gc());
  return c;
  }
}using namespace chtholly;
using namespace std;
typedef pair<int,int> edge;
const int yuzu=1e5;
typedef int fuko[yuzu|10];
vector<int> lj[yuzu|10];
fuko dfn,low,vis,col,du,dp,a,r;
stack<int> lxy;
int cnt,ans,n,m;
edge e[yuzu<<3|10];

void tarjan(int u){
dfn[u]=low[u]=++cnt;
vis[u]=1,lxy.push(u);
for (int v:lj[u]){
  if (!dfn[v]||vis[v]){
    if (!dfn[v]) tarjan(v);
    low[u]=min(low[u],low[v]);
    }
  }
if (dfn[u]==low[u]){
  col[u]=++ans,vis[u]=0;
  for (;lxy.top()^u;lxy.pop()){
    vis[lxy.top()]=0;
    col[lxy.top()]=ans;
    r[ans]+=a[lxy.top()];
    }
  r[ans]+=a[u],lxy.pop();
  }
}

void dfs(int u){
if (dp[u]) return;
dp[u]=r[u];
int zxy=0;
for (int i:lj[u]){
  if (!dp[i]) dfs(i);
  zxy=max(zxy,dp[i]);
  }dp[u]+=zxy;
}

int main(){
int i,j;n=read(),m=read();
for (i=1;i<=n;++i) a[i]=read();
for (i=1;i<=m;++i){
  int u=read(),v=read();
  lj[u].push_back(v);
  e[i]=edge(u,v);
  }
for (i=1;i<=n;++i){
  if (!dfn[i]) tarjan(i);
  } 
for (i=1;i<=n;++i) lj[i].clear();
for (i=1;i<=m;++i){
  int u=e[i].first,v=e[i].second;
  if (col[v]^col[u]) lj[col[u]].push_back(col[v]);
  }
int llx=0;
for (i=1;i<=ans;++i){
  if (!dp[i]) dfs(i),llx=max(llx,dp[i]);
  }write(llx);
}

求割点和割边.

这里就只讲求割点了.
如果在无向图中去掉某个点u使得该图不连通了,则u就是该图的一个割点.
很明显了.我画个图来看一看.

标红星的就是个割点.显然割点之后的点连边不可能连到比该割点 d f n dfn dfn要小的点.
据此修改 t a r j a n tarjan tarjan的代码如下.

fuko dfn,low,vis,is_cut;
int cnt,ans;

int tarjan(int u,int fa){
int ch=0,lowu;
dfn[u]=lowu=++cnt;
for (int v:lj[u]){
  if (!dfn[v]){
    ++ch;
    int lowv=tarjan(v,u);
    lowu=min(lowu,lowv);
    if (lowv>dfn[u]) is_cut[u]=1;
    }
  else{
    if (v^fa&&dfn[v]<dfn[u]){
      lowu=min(lowu,dfn[v]);
      }
    }
  } 
if (!fa&&ch==1) is_cut[u]=0;//注意如果根节点有两个或者以上的儿子,根节点是割点.
return low[u]=lowu;
}

然后瞎搞过模板.
好了.谢谢大家.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值