tarjan 缩点(模板)
描述:
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
注:允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
思路:
tarjan 的模板之一——缩点。先利用 tarjan 出图中的强连通分量及大小(点的权值),然后遍历所有点,重新构图(←重点),根据 topo DP一下,就可得出图中最大的权值和。
标程:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<string> #include<cstdlib> #include<stack> #include<vector> #include<queue> #include<deque> #include<map> #include<set> using namespace std; #define maxn 1000005 int n,m,cnt1,num,top,cnt2;//cnt1 作原图的前向星,cnt2 作新图的 int ins[maxn],head[maxn],nu[maxn],dfn[maxn],low[maxn];//nu 用来去除重边(重构图用),ins 记录强连通分量的大小 int st[maxn],co[maxn];//栈只为了表示此时是否有父子关系,co判断该点是否在栈中 int h[maxn],in[maxn],dis[maxn];//h相当于新图前向星的head,in统计点的入度,dis记录权值和(in,dis都做topo排序用) int ans=0;//统计答案 struct hh { int to,next,from;//from,to有可以分别记录边的起点和终点 }t1[maxn],t2[maxn];//t1原图,t2新图 inline int read() { int kr=1,xs=0; char ls; ls=getchar(); while(!isdigit(ls)) { if(!(ls^45)) kr=-1; ls=getchar(); } while(isdigit(ls)) { xs=(xs<<1)+(xs<<3)+(ls^48); ls=getchar(); } return xs*kr; } inline void add(int x,int y) { t1[++cnt1].next=head[x]; t1[cnt1].from=x; t1[cnt1].to=y; head[x]=cnt1; }//存原图 inline void tarjan(int x) { low[x]=dfn[x]=++num; st[++top]=x;co[x]=1; for (int i=head[x];i;i=t1[i].next) { int v=t1[i].to; if(!dfn[v]) { tarjan(v); low[x]=min(low[x],low[v]); } else if(co[v]) { low[x]=min(low[x],low[v]); } } if (dfn[x]==low[x]) { int y; while(y=st[top]) { nu[y]=x;//表示:可以从x直接到达y(单向) co[y]=0;//y出栈(记录清除) if(x==y) break; ins[x]+=ins[y];//合并两个强连通分量 --top; } --top; } }//日常操作 inline void topo() { queue <int> q; int tot=0; for (int i=1;i<=n;i++) if (nu[i]==i&&!in[i])//该点自己到达自己,且入度为0,表示为一个被缩为一点的强连通分量(且为起始点) { q.push(i); dis[i]=ins[i]; } while (!q.empty())//依次取出所有的起点(入度为0的点) { int k=q.front();q.pop(); for (int i=h[k];i;i=t2[i].next)//遍历可以到达的点 { int v=t2[i].to; dis[v]=max(dis[v],dis[k]+ins[v]);//更新答案 in[v]--;//入度-- if(in[v]==0) q.push(v);//减到这个点入度为0,扔进队列,下次再取出作为起点 } } for (int i=1;i<=n;i++) ans=max(ans,dis[i]);//更新最终答案 } int main() { n=read();m=read(); for (int i=1;i<=n;i++) ins[i]=read();//初始每个点认为是一个强连通分量(假装是) for (int i=1;i<=m;i++) { int u,v; u=read();v=read(); add(u,v); } for (int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for (int i=1;i<=m;i++) { int x=nu[t1[i].from],y=nu[t1[i].to]; if (x!=y)//←可以去除重边 { t2[++cnt2].next=h[x]; t2[cnt2].to=y; t2[cnt2].from=x; h[x]=cnt2;//重构新图 in[y]++; } } topo();//topo 排序 printf("%d",ans);//输出 return 0; }