tarjan--P3387 模板缩点
Posted very-beginning
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tarjan--P3387 模板缩点相关的知识,希望对你有一定的参考价值。
题目背景
缩点+DP
题目描述
给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n,m
第二行 n 个整数,依次代表点权
第三至 m+2 行,每行两个整数 u,v,表示一条 u→v的有向边。
输出格式
共一行,最大的点权之和。
*预备知识:有向图,强连通。
*有向图:由有向边的构成的图。需要注意的是这是Tarjan算法的前提和条件。
*强连通:如果两个顶点可以相互通达,则称两个顶点 强连通(strongly connected)。如果有向图G的每两个顶点都 强连通,称G是一个强连通图。非 强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
两个重要的数组:
*dfn[ ]:就是一个时间戳(被搜到的次序),一旦某个点被DFS到后,这个时间戳就不再改变(且每个点只有唯一的时间戳)。所以常根据dfn的值来判断是否需要进行进一步的深搜。
*low[ ]:该子树中,且仍在栈中的最小时间戳,像是确立了一个关系,low[ ]相等的点在同一强连通分量中。
注意初始化时 dfn[ ] = low[ ] = ++cnt.
算法思路:
首先这个图不一定是一个连通图,所以跑Tarjan时要枚举每个点,若dfn[ ] == 0,进行深搜。然后对于搜到的点寻找与其有边相连的点,判断这些点是否已经被搜索过,若没有,则进行搜索。若该点已经入栈,说明形成了环,则更新low.在不断深搜的过程中如果没有路可走了(出边遍历完了),那么就进行回溯,回溯时不断比较low[ ],去最小的low值。如果dfn[x]==low[x]则x可以看作是某一强连通分量子树的根,也说明找到了一个强连通分量,然后对栈进行弹出操作,直到x被弹出。
局部代码:
1 void tarjan(int now) 2 { 3 dfn[now]=low[now]=++cnt; //初始化 4 stack[++t]=now; //入栈操作 5 v[now]=1; //v[]代表该点是否已入栈 6 for(int i=f[now];i!=-1;i=e[i].next) //邻接表存图 7 if(!dfn[e[i].v]) //判断该点是否被搜索过 8 { 9 tarjan(e[i].v); 10 low[now]=min(low[now],low[e[i].v]); //回溯时更新low[ ],取最小值 11 } 12 else if(v[e[i].v]) 13 low[now]=min(low[now],dfn[e[i].v]); //一旦遇到已入栈的点,就将该点作为连通量的根 14 //这里用dfn[e[i].v]更新的原因是:这个点可能 15 //已经在另一个强连通分量中了但暂时尚未出栈,所 16 //以now不一定能到达low[e[i].v]但一定能到达 17 //dfn[e[i].v]. 18 if(dfn[now]==low[now]) 19 { 20 int cur; 21 do 22 { 23 cur=stack[t--]; 24 v[cur]=false; //不要忘记出栈 25 }while(now!=cur); 26 } 27 }
思路:先缩点,新节点的值等于强连通分量中所有节点权值之和。缩点之后就由原图得到了一张新图(其实是一棵树),然后再dfs,求出从根节点到叶子节点权值之和的最大值(dp)
这道题的两份代码(*注):
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #include<string> 7 #include<cstdlib> 8 #include<vector> 9 #include<queue> 10 using namespace std; 11 #define maxn 1000005 12 int n,m,cnt1,num,top,cnt2;//cnt1 作原图的前向星,cnt2 作新图的 13 int ins[maxn],head[maxn],nu[maxn],dfn[maxn],low[maxn];//nu 用来去除重边(重构图用),ins 记录强连通分量的大小 14 int st[maxn],co[maxn];//栈只为了表示此时是否有父子关系,co判断该点是否在栈中 15 int h[maxn],in[maxn],dis[maxn];//h相当于新图前向星的head,in统计点的入度,dis记录权值和(in,dis都做topo排序用) 16 int ans=0;//统计答案 17 struct hh 18 { 19 int to,next,from;//from,to有可以分别记录边的起点和终点 20 }t1[maxn],t2[maxn];//t1原图,t2新图 21 int read() 22 { 23 int x = 1,a = 0; 24 char ch = getchar(); 25 while(ch < ‘0‘ || ch > ‘9‘){//如果读进来的不是数字…… 26 if(ch == ‘-‘)x = -1;//判断负号 27 ch = getchar(); 28 } 29 while(ch <= ‘9‘&&ch >= ‘0‘){//如果读进来的是数字…… 30 a = a * 10 + ch - ‘0‘; 31 ch = getchar(); 32 } 33 return x*a; 34 } 35 inline void add(int x,int y) 36 { 37 t1[++cnt1].next=head[x]; 38 t1[cnt1].from=x; 39 t1[cnt1].to=y; 40 head[x]=cnt1; 41 }//存原图 42 inline void tarjan(int x) 43 { 44 low[x]=dfn[x]=++num; 45 st[++top]=x;co[x]=1; 46 for (int i=head[x];i;i=t1[i].next) 47 { 48 int v=t1[i].to; 49 if(!dfn[v]) 50 { 51 tarjan(v); 52 low[x]=min(low[x],low[v]); 53 } 54 else if(co[v]) 55 { 56 low[x]=min(low[x],low[v]); 57 } 58 } 59 if (dfn[x]==low[x]) 60 { 61 int y; 62 while(y=st[top]) 63 { 64 nu[y]=x;//表示:可以从x直接到达y(单向) 65 co[y]=0;//y出栈(记录清除) 66 if(x==y) break; 67 ins[x]+=ins[y];//合并两个强连通分量 68 --top; 69 } 70 --top; 71 } 72 }//日常操作 73 inline void topo() 74 { 75 queue <int> q; 76 int tot=0; 77 for (int i=1;i<=n;i++) 78 if (nu[i]==i&&!in[i])//该点自己到达自己,且入度为0,表示为一个被缩为一点的强连通分量(且为起始点) 79 { 80 q.push(i); 81 dis[i]=ins[i]; 82 } 83 while (!q.empty())//依次取出所有的起点(入度为0的点) 84 { 85 int k=q.front();q.pop(); 86 for (int i=h[k];i;i=t2[i].next)//遍历可以到达的点 87 { 88 int v=t2[i].to; 89 dis[v]=max(dis[v],dis[k]+ins[v]);//更新答案 90 in[v]--;//入度-- 91 if(in[v]==0) q.push(v);//减到这个点入度为0,扔进队列,下次再取出作为起点 92 } 93 } 94 for (int i=1;i<=n;i++) 95 ans=max(ans,dis[i]);//更新最终答案 96 } 97 int main() 98 { 99 n=read();m=read(); 100 for (int i=1;i<=n;i++) 101 ins[i]=read();//初始每个点认为是一个强连通分量(假装是) 102 for (int i=1;i<=m;i++) 103 { 104 int u,v; 105 u=read();v=read(); 106 add(u,v); 107 } 108 for (int i=1;i<=n;i++) 109 if(!dfn[i]) 110 tarjan(i); 111 for (int i=1;i<=m;i++) 112 { 113 int x=nu[t1[i].from],y=nu[t1[i].to]; 114 if (x!=y)//←可以去除重边 115 { 116 t2[++cnt2].next=h[x]; 117 t2[cnt2].to=y; 118 t2[cnt2].from=x; 119 h[x]=cnt2;//重构新图 120 in[y]++; 121 } 122 } 123 topo();//topo 排序 124 printf("%d",ans);//输出 125 return 0; 126 }
1 #include <cstdio> 2 #include <iostream> 3 #include <vector> 4 #include<cstring> 5 using namespace std; 6 const int M=1e4+20; 7 int dfn[M],low[M],in[M],q[M],tot,top; 8 int n,m,a[M],b[M],belong[M],sum,vis[M]; //belong数组用来保存原节点合成到哪一个新节点中 9 vector<int> g1[M],g2[M]; 10 void tarjan(int x){ 11 dfn[x]=low[x]=++tot; 12 q[top++]=x; 13 in[x]=1; 14 for(int i=0;i<g1[x].size();i++){ 15 int v=g1[x][i]; 16 if(!dfn[v]){ 17 tarjan(v); 18 low[x]=min(low[x],low[v]); 19 } 20 else if(in[v])low[x]=min(low[x],low[v]); 21 } 22 if(low[x]==dfn[x])//一个强连通分量 23 { 24 sum++; 25 int v; 26 do{ 27 v=q[--top]; 28 in[v]=0; 29 belong[v]=sum; //将同一个强连通分量中的节点合成到同一个新节点中 30 b[sum]+=a[v]; 31 } 32 while(v!=x); 33 } 34 } 35 void build(){ 36 for(int i=1;i<=n;i++){ 37 for(int j=0;j<g1[i].size();j++){ 38 int v=g1[i][j]; 39 if(belong[i]!=belong[v]){ 40 g2[belong[i]].push_back(belong[v]); 41 } 42 } 43 } 44 } 45 int dfs(int s){ 46 vis[s]=1; 47 if(g2[s].size()==0) 48 return b[s]; 49 int ans=-1; 50 for(int i=0;i<g2[s].size();i++){ 51 ans=max(ans,dfs(g2[s][i])+b[s]); 52 } 53 return ans; 54 } 55 int main(){ 56 scanf("%d%d",&n,&m); 57 for(int i=1;i<=n;i++)scanf("%d",a+i); 58 int x,y; 59 while(m--){ 60 scanf("%d%d",&x,&y); 61 g1[x].push_back(y); 62 } 63 for(int i=1;i<=n;i++) 64 if(!dfn[i])tarjan(i); 65 build(); 66 int ans=-1; 67 for(int i=1;i<=sum;i++) 68 if(!vis[i])ans=max(ans,dfs(i)); 69 printf("%d ",ans); 70 return 0; 71 }
以上是关于tarjan--P3387 模板缩点的主要内容,如果未能解决你的问题,请参考以下文章