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 模板缩点的主要内容,如果未能解决你的问题,请参考以下文章

P3387 模板缩点 [强连通分量][DAG]

P3387 模板缩点

LUOGU P3387 模板缩点 (缩点+DAG dp)

洛谷 P3387 模板缩点

洛谷P3387 模板缩点

P3387 模板缩点