1.什么是强连通份量?:html
有向图强连通份量:在有向图G中,若是两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。若是有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通份量(strongly connected components)。——度娘c++
显然正确,可是文绉绉的又难懂。算法
其实就是有一大团点,它们之间能够互相到达,且没有其它的点与它们互可到达,那么这一大团点就是有向图的一个强联通份量。数组
一个强连通份量能够看做一个或几个环拼接在一块儿。学习
如:spa
2.如何操做?:code
首先对于整个有向图进行dfs,若dfs树中存在子树有到达父亲节点,从父亲到这个点则均可互相到达,这些点有可能与父亲原属的强连通图合并,也有可能成为新的一个强连通份量。component
如上图。htm
因此咱们判断一个点是否在一个强连通份量中,就用它是否能到达dfs序比他小的点。blog
但咱们如何将一个强连通份量的点记录呢?
答:用一个栈储存,遇到一个不能到达dfs序比他小的点的点,就将栈里的点都标记为一个新的强连通份量,再清空栈(一个点咱们也看做一个强连通份量)。
咱们用dfn数组记录dfs序,low数组记录能到达的点最小的dfs编号。
对于每个点,须要用它的儿子的low来更新本身的low,如上图中的2号点;
然而,如有连向父亲的边呢?是否是直接用父亲的dfn来更新本身的low?
固然是对的!!!(做者就曾经错在这里,曾今觉得是对的,但打模板时错了,改了两处,结果觉得是由于将dfn改成low才对的,后面发现不是,果真我仍是太蒟蒻了qaq~~~~~)
在tarjan算法中,都是用父亲的dfn来更新本身的low,
但在有向图中,若本身的父亲有连向祖父等祖宗,那么本身和祖宗在同一个强连通份量;
而在无向图中,则属于两个强连通份量。
如:
其中,一、二、三、四、5互可到达,只有dfn[1]==low[1],都是一个强连通份量。
此时,用low来更新错误
还有,注意:对于已经属于一个强连通份量的点,不能用它的low来更新连向它的点!
因此,low[5]为5。
1 void tarjan(int u)//当前点 2 { 3 low[u]=dfn[u]=++deep; x[++top]=u; v[u]=1; 4 for(int i=head1[u];i;i=e[i].nxt) 5 { 6 if(!dfn[e[i].to]) tarjan(e[i].to),low[u]=min(low[u],low[e[i].to]);//连向儿子的边 7 else if(v[e[i].to]) low[u]=min(low[u],low[e[i].to]);//连向父亲且不属于一个强连通份量 8 } 9 if(dfn[u]==low[u])//找到一个强连通份量 10 { 11 num[u]=++tot; v[u]=0; 12 while(x[top]!=u) v[x[top]]=0,num[x[top]]=tot,--top; 13 --top; 14 } 15 } 16 int main() 17 { 18 for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 19 return 0; 20 }
最后,模拟一个图的tarjan过程:
一共有一个强连通份量:1.{1,2,3,4,5,6,7,8,9}。
u=1:dfn[1]=1,++top,x[1]=1,tarjan(8);
u=8:dfn[8]=2,++top,x[2]=8,tarjan(9);
u=9:dfn[9]=3,++top,x[3]=9,low[9]=1;
u=8:low[8]=1;
u=1:tarjan(2);
u=2:dfn[2]=4,++top,x[4]=2,tarjan(3);
u=3:dfn[3]=5,++top,x[5]=3,tarjan(4);
u=4:dfn[4]=6,++top,x[6]=4,low[4]=low[2]=4;
u=3:low[3]=low[4]=4;
u=2:low[2]=4,tarjan(5);
u=5:dfn[5]=7,++top,x[7]=5,tarjan(6);
u=6:dfn[6]=8,++top,x[8]=6,tarjan(7);
u=7:dfn[7]=9,++top,x[9]=7,low[7]=1;
u=6:low[6]=1;
u=5:low[5]=1;
u=2:low[2]=1;
u=1:dfn[1]==low[1]=1→++tot,将x[1]到x[9]标记为tot=1,top=0;
结束。
3.缩点:
将每一个强联通份量看做一点,将全部连向强联通份量内的点连向这个强联通份量。
1 int main() 2 { 3 n=read(); m=read(); 4 for(int i=1;i<=n;++i) w1[i]=read(); 5 for(re int i=1;i<=m;++i) t1=read(),t2=read(),add(t1,t2,e,head1); 6 cnt=0; 7 for(int i=1;i<=n;++i) 8 { 9 w2[num[i]]+=w1[i]; 10 for(int j=head1[i];j;j=e[j].nxt) if(num[i]!=num[e[j].to]) add(num[i],num[e[j].to],f,head2),++rd[num[e[j].to]]; 11 } 12 return 0; 13 }
缩点+DP
给定一个n个点m条边有向图,每一个点有一个权值,求一条路径,使路径通过的点权值之和最大。你只须要求出这个权值和。
容许屡次通过一条边或者一个点,可是,重复通过的点,权值只计算一次。
输入格式:
第一行,n,m
第二行,n个整数,依次表明点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
n<=10^4,m<=10^5,0<=点权<=1000
算法:Tarjan缩点+DAGdp
题解:
当进入一个强连通份量时,确定将这个强连通份量都走一遍,因此缩点,变为一个DAG(有向无环图),而后按拓扑序dp,每一个点的答案为全部能到达它的点的答案的最大值+本身的点值,输出全部点中答案的最大值。
用每一个点的答案更新它所能到达的全部点的答案。
代码以下:
1 #include<bits/stdc++.h> 2 #define re register 3 using namespace std; 4 const int N=10006,M=100006; 5 int n,m,t1,t2,w1[N],w2[N],num[N],cnt=0,head1[N],t,tot=0,head2[N],x[N],dfn[N],low[N],ans[N],maxa=-1,rd[N],deep=0,top=0,v[N]; 6 struct edge 7 { 8 int to,nxt; 9 }e[M],f[M]; 10 inline void add(int u,int v,edge a[],int head[]){a[++cnt].to=v,a[cnt].nxt=head[u],head[u]=cnt;} 11 inline int read() 12 { 13 int T=0,F=1; char ch=getchar(); 14 while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();} 15 while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar(); 16 return F*T; 17 } 18 void tarjan(int u) 19 { 20 low[u]=dfn[u]=++deep; x[++top]=u; v[u]=1; 21 for(int i=head1[u];i;i=e[i].nxt) 22 { 23 if(!dfn[e[i].to]) tarjan(e[i].to),low[u]=min(low[u],low[e[i].to]); 24 else if(v[e[i].to]) low[u]=min(low[u],low[e[i].to]); 25 } 26 if(dfn[u]==low[u]) 27 { 28 num[u]=++tot; v[u]=0; 29 while(x[top]!=u) v[x[top]]=0,num[x[top]]=tot,--top; 30 --top; 31 } 32 } 33 void tppx() 34 { 35 queue<int> q; cnt=0; 36 for(int i=1;i<=tot;++i) if(!rd[i]) x[++cnt]=i,q.push(i); 37 while(!q.empty()) 38 { 39 int tmp=q.front(); q.pop(); 40 for(int i=head2[tmp];i;i=f[i].nxt) 41 { 42 --rd[f[i].to]; 43 if(!rd[f[i].to]) x[++cnt]=f[i].to,q.push(f[i].to); 44 } 45 } 46 } 47 int main() 48 { 49 n=read(); m=read(); 50 for(int i=1;i<=n;++i) w1[i]=read(); 51 for(re int i=1;i<=m;++i) t1=read(),t2=read(),add(t1,t2,e,head1); 52 for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 53 cnt=0; 54 for(int i=1;i<=n;++i) 55 { 56 w2[num[i]]+=w1[i]; 57 for(int j=head1[i];j;j=e[j].nxt) if(num[i]!=num[e[j].to]) add(num[i],num[e[j].to],f,head2),++rd[num[e[j].to]]; 58 } 59 memset(x,0,sizeof(x)); tppx(); 60 for(int i=1;i<=n;++i) 61 { 62 t=x[i]; ans[t]=max(ans[t],w2[t]); 63 maxa=max(maxa,ans[t]); 64 for(int j=head2[t];j;j=f[j].nxt) ans[f[j].to]=max(ans[f[j].to],ans[t]+w2[f[j].to]); 65 } 66 printf("%d\n",maxa); 67 return 0; 68 }