缩点(tarjan求强连通分量)
Posted ppprseter
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缩点(tarjan求强连通分量)相关的知识,希望对你有一定的参考价值。
P3387 【模板】缩点
题目背景
缩点+\(DP\)
题目描述
给定一个\(n\)个点\(m\)条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,\(n,m\)
第二行,\(n\)个整数,依次代表点权
第三至\(m+2\)行,每行两个整数\(u,v\),表示\(u->v\)有一条有向边
输出格式:
共一行,最大的点权之和。
说明
\(n<=10^4,m<=10^5\),点权\(<=1000\)
算法:\(Tarjan\)缩点+\(DAGdp\)
重点在于tarjan求解强连通分量。
定义:
- 有向图强连通分量:在有向图\(G\)中,如果两个顶点\(v_i,v_j\)间\((v_i>v_j)\)有一条从\(v_i\)到\(v_j\)的有向路径,同时还有一条从\(v_j\)到\(v_i\)的有向路径,则称两个顶点强连通。
- 如果有向图\(G\)的每两个顶点都强连通,称\(G\)是一个强连通图。
- 有向图的极大强连通子图,称为强连通分量。
说明:单个点也可以是强联通分量。
对每个点\(i\)维护两个值,\(dfn[i]\)和\(low[i]\),前者代表时间戳,表示\(i\)是第几个被访问的(用全局变量维护),后者表示最小时间,即\(i\)能到达的点中\(dfn\)最小者。
访问到点\(i\)时,我们先把\(i\)放入栈中,然后令\(dfn[i]=low[i]=\)当前时间,去遍历\(i\)点能到达的点(值得注意的是,这个点必须在栈里面),看是否能够更新\(low[i]\)。
栈:我们求解某次强连通分量时,存储在一个强连通分量的点集。点\(i\)某个能遍历的点\(j\)在栈里面对应的即是\(j\)对\(i\)是联通的。
当\(i\)能到达的点遍历完毕时,若\(dfn[i]!=low[i]\),即它能到达一个更早的时间点,作为这个强联通分量的使点。
若\(dfn[i]==low[i]\),则这个点可以作为某块强连通分量的开始点。
我们把栈中的元素取出来,直到栈顶为\(i\),则这些取出来的点连同\(i\)即构成了一块强连通分量。
感性认识:用两个值维护了点的相互连通性。
我们把点缩好以后,做拓扑排序dp就行了。
code:
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M=100010;
int min(int x,int y) {return x<y?x:y;}
int max(int x,int y) {return x>y?x:y;}
struct Edge{int to,next;}edge0[M*2],edge[M*2];
int head0[M],cnt0=0,head[M],cnt=0;
int c0[M],c[M],n,m;
void add0(int u,int v){edge0[++cnt0].next=head0[u];edge0[cnt0].to=v;head0[u]=cnt0;}
void add(int u,int v){edge[++cnt].next=head[u];edge[cnt].to=v;head[u]=cnt;}
int s[M],tot=0;
void push(int x){s[++tot]=x;}
void pop(){tot--;}
int in[M],ha[M],cntt=0,dfn[M],low[M],time=0,vis[M];
void tarjan(int now)
{
push(now);
vis[now]=1;
dfn[now]=low[now]=++time;
for(int i=head0[now];i;i=edge0[i].next)
{
int v=edge0[i].to;
if(!dfn[v]) {tarjan(v);low[now]=min(low[now],low[v]);}
else if(vis[v]) low[now]=min(low[now],dfn[v]);
}
if(dfn[now]==low[now])
{
cntt++;
while(s[tot]!=now)
{
vis[s[tot]]=0;
c[cntt]+=c0[s[tot]];
ha[s[tot]]=cntt;
pop();
}
ha[s[tot]]=cntt;
vis[s[tot]]=0;
c[cntt]+=c0[s[tot]];
pop();
}
}
queue <int > q;
int dp[M],ans=0;
void topo()
{
for(int i=1;i<=cntt;i++)
if(!in[i])
{
dp[i]=c[i];
ans=max(dp[i],ans);
q.push(i);
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
in[v]--;
dp[v]=max(dp[u]+c[v],dp[v]);
if(!in[v]) {ans=max(ans,dp[v]);q.push(v);}
}
}
}
int main()
{
int u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",c0+i);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
add0(u,v);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
for(int j=head0[i];j;j=edge0[j].next)
{
int v=edge0[j].to;
if(ha[v]!=ha[i])
{
add(ha[i],ha[v]);
in[ha[v]]++;
}
}
topo();
printf("%d\n",ans);
return 0;
}
2018.6.6
以上是关于缩点(tarjan求强连通分量)的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ10511051: [HAOI2006]受欢迎的牛 tarjan求强连通分量+缩点