知识点
强连通分量:是对于有向图来讲的。一个强连通分量定义为一个点集V与他们之间的边集E所组成的集合二元对(V,E),并满足若点x,y∈V,那么x,y可以互相到达。极大强连通分量定义为一个不被任何其它强连通分量所包含的强连通分量。
Tarjan算法:是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
接下来是对算法流程的演示:
定义:
- D F N ( u ) DFN(u) DFN(u)为节点 u u u搜索的次序编号(时间戳)
- L o w ( u ) Low(u) Low(u)为 u u u或 u u u的子树能够追溯到的最早的栈中节点的次序号。
- 当 D F N ( u ) = L o w ( u ) DFN(u)=Low(u) DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
从节点
1
1
1开始
D
F
S
DFS
DFS,把遍历到的节点加入栈中。搜索到节点
u
=
6
u=6
u=6时,
D
F
N
[
6
]
=
L
O
W
[
6
]
DFN[6]=LOW[6]
DFN[6]=LOW[6],找到了一个强连通分量
6
{6}
6,退栈。
返回节点5,发现
D
F
N
[
5
]
=
L
O
W
[
5
]
DFN[5]=LOW[5]
DFN[5]=LOW[5],退栈后
5
{5}
5为一个强连通分量。
返回节点
3
3
3,继续搜索到节点4,把4加入堆栈。节点1还在栈中,所以
L
O
W
[
4
]
=
1
LOW[4]=1
LOW[4]=1。节点6已经出栈,返回3,
(
3
,
4
)
(3,4)
(3,4)为树枝边,所以
L
O
W
[
3
]
=
L
O
W
[
4
]
=
1
LOW[3]=LOW[4]=1
LOW[3]=LOW[4]=1。
继续回到节点1,最后访问节点2。访问边
(
2
,
4
)
,
4
(2,4),4
(2,4),4还在栈中,所以
L
O
W
[
2
]
=
D
F
N
[
4
]
=
5
LOW[2]=DFN[4]=5
LOW[2]=DFN[4]=5。返回1后,发现
D
F
N
[
1
]
=
L
O
W
[
1
]
DFN[1]=LOW[1]
DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量
1
,
3
,
4
,
2
{1,3,4,2}
1,3,4,2。
至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。可以发现,运行
T
a
r
j
a
n
Tarjan
Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为
O
(
N
+
M
)
O(N+M)
O(N+M)。
PS:图中的栈是从上而下的。。。见谅。。。
原代码
void Tarjan(int x)
{
dfn[x]=low[x]=++t;//为节点u设定次序编号和Low初值
st[++top]=x;//将节点u压入栈中
for(int i=head[x];i;i=a[i].next)//枚举每一条边
{
int y=a[i].x;
if(!dfn[y])//如果节点v未被访问过
{
Tarjan(y);//继续向下找
low[x]=min(low[x],low[y]);
}
else if(!c[y])//如果节点v还在栈内
low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x])//如果节点u是强连通分量的根
{
c[x]=++tot;
sum[tot]+=lyx[x];
while(st[top]!=x)
sum[tot]+=lyx[st[top]],c[st[top--]]=tot;
top--;//将v退栈,为该强连通分量中一个顶
}
}
解题思路
用 d i s i dis_i disi表示点i的点权,用 f i f_i fi表示以点i为终点的路径所经过的最大点权和。则转移方程为 f v = m a x ( f u + d i s v , f v ) f_v=max(f_u+dis_v,f_v) fv=max(fu+disv,fv)
考虑优化DP的过程,如果点对(u,v)可以互相到达,就称他们是强连通的,我们把所有强连通的点进行捆绑,缩点,变成一个有向无环图,就可以用拓扑排序进行DP。
代码
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int n,m,k,tot,top,t,ans;
int u[10010],v[10010],st[10010],sum[10010],head[100010],lyx[10010],dfn[10010],low[10010],c[10010],ru[10010],f[10010];
struct c{
int x,next;
}a[100100];
void add(int x,int y){
a[++k].x=y;
a[k].next=head[x];
head[x]=k;
}
void Tarjan(int x)
{
dfn[x]=low[x]=++t;
st[++top]=x;
for(int i=head[x];i;i=a[i].next)
{
int y=a[i].x;
if(!dfn[y])
{
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(!c[y])
low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x])
{
c[x]=++tot;
sum[tot]+=lyx[x];
while(st[top]!=x)
sum[tot]+=lyx[st[top]],c[st[top--]]=tot;
top--;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&lyx[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u[i],&v[i]);
add(u[i],v[i]);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
Tarjan(i);
}
memset(head,0,sizeof(head));
memset(a,0,sizeof(a));
k=0;
for(int i=1;i<=m;i++)
{
if(c[u[i]]!=c[v[i]])
{
add(c[u[i]],c[v[i]]);
ru[c[v[i]]]++;
}
}
queue<int>q;
for(int i=1;i<=tot;i++)
{
f[i]=sum[i];
if(!ru[i])q.push(i);
}
while(!q.empty())
{
int y=q.front();
q.pop();
for(int i=head[y];i;i=a[i].next)
{
int w=a[i].x;
f[w]=max(f[w],f[y]+sum[w]);
ru[w]--;
if(!ru[w])q.push(w);
}
}
for(int i=1;i<=tot;i++)
ans=max(ans,f[i]);
printf("%d",ans);
}