P3387 【模板】缩点(Tarjan求强连通分量)
题目背景
缩点+DP
题目描述
给定一个 nnn 个点 mmm 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n,mn,mn,m
第二行 nnn 个整数,依次代表点权
第三至 m+2m+2m+2 行,每行两个整数 u,vu,vu,v,表示一条 u→vu\rightarrow vu→v 的有向边。
输出格式
共一行,最大的点权之和。
输入输出样例
输入 #1
2 2 1 1 1 2 2 1
输出 #1
2
板子题因为脑瘫错误调了半天...
注意到题干描述,很容易看出是要缩点,跑一遍Tarjan后我们就得到了一个DAG,DAG有点类似树的结构,只需要从每个入度为0的点(有点类似树根)跑dfs就行了,记得记忆化。
#include <bits/stdc++.h> using namespace std; const int N = 10005; const int M = 100005; int ver[M],Next[M],head[N],dfn[N]={0},low[N]; int vc[M],nc[M],hc[N],tc=0; int sstack[N],ins[N],c[N]; int point[N],point_c[N]={0}; vector<int>scc[N]; int n,m,tot=0,num=0,top=0,cnt=0; int ans[N]={0}; void add(int x, int y) { ver[++tot]=y,Next[tot]=head[x],head[x]=tot; } void add_c(int x,int y) { vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc; } void tarjan(int x) { dfn[x]=low[x]=++num; sstack[++top]=x,ins[x]=1; int i; for(i=head[x];i;i=Next[i]) { if(!dfn[ver[i]]) { tarjan(ver[i]); low[x]=min(low[x],low[ver[i]]); } else if(ins[ver[i]]) low[x]=min(low[x],dfn[ver[i]]); } if(dfn[x]==low[x]) { cnt++; int y; do { y=sstack[top--],ins[y]=0; c[y]=cnt,scc[cnt].push_back(y); }while(x!=y); } } void dfs(int x,int pre)//得用缩点后的新图 { int i; ans[x]=max(ans[x],ans[pre]+point_c[x]); for(i=hc[x];i;i=nc[i]) { if(vc[i]!=pre)dfs(vc[i],x); } } int main() { cin>>n>>m; int i,j,x; for(i=1;i<=n;i++)scanf("%d",&point[i]); for(i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); } for(i=1;i<=n;i++) { if(!dfn[i])tarjan(i); } int deg[N]={0}; for(x=1;x<=n;x++) { for(i=head[x];i;i=Next[i]) { int y=ver[i]; if(c[x]==c[y])continue; add_c(c[x],c[y]); deg[c[y]]++;//deg表示入度 别写成deg[y]++ } } for(i=1;i<=cnt;i++) { for(j=0;j<scc[i].size();j++) { point_c[i]+=point[scc[i][j]]; } } //新图是一个有向无环图 找入度为0的点 ans[0]=0; for(i=1;i<=cnt;i++) { if(deg[i]==0)//不能只从入度为0的点开始遍历 { dfs(i,0); } } int mmax=0; for(i=1;i<=cnt;i++) { mmax=max(mmax,ans[i]); } cout<<mmax<<endl; return 0; }