洛谷 P3387 【模板】缩点 题目 分析 代码
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式
共一行,最大的点权之和。
输入输出样例
输入 #1
2 2 1 1 1 2 2 1
输出 #1
2
说明/提示
n<=10^4,m<=10^5,0<=点权<=1000
算法:Tarjan缩点+DAGdp
分析
- tarjan模板,因为经过点可以重复,但是权值只能纪录一次
- 那么我们将环缩成一个点
- 然后直接跑记忆化
- 找到最大值就好了
代码
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 struct sb 5 { 6 int to,nx; 7 }g[200001]; 8 int x[100001],y[100001],t[100001]; 9 int cnt,list[200001]; 10 void add(int x,int y) 11 { 12 g[++cnt].to=y; g[cnt].nx=list[x]; list[x]=cnt; 13 } 14 int dfn[100001],low[100001],num,stack[100010],top,color[100010],col,sum[100010],vis[100010]; 15 void tarjan(int x) 16 { 17 dfn[x]=++num; 18 low[x]=num; 19 vis[x]=1; 20 stack[++top]=x; 21 for (int i=list[x];i;i=g[i].nx) 22 { 23 int y=g[i].to; 24 if (!dfn[y]) 25 { 26 tarjan(y); 27 low[x]=min(low[x],low[y]); 28 } 29 else if (vis[y]) low[x]=min(low[x],dfn[y]); 30 } 31 if (dfn[x]==low[x]) 32 { 33 ++col; 34 while (stack[top+1]!=x) 35 { 36 color[stack[top]]=col; 37 sum[col]+=t[stack[top]]; 38 vis[stack[top--]]=false; 39 } 40 } 41 } 42 int f[100010]; 43 void dfs(int x) 44 { 45 if (f[x]) return; 46 f[x]=sum[x]; 47 int maxsum=0; 48 for (int i=list[x];i;i=g[i].nx) 49 { 50 int y=g[i].to; 51 if (!f[y]) dfs(y); 52 maxsum=max(maxsum,f[y]); 53 } 54 f[x]+=maxsum; 55 } 56 int main () 57 { 58 int n,m; 59 cin>>n>>m; 60 for (int i=1;i<=n;i++) cin>>t[i]; 61 for (int i=1;i<=m;i++) 62 { 63 cin>>x[i]>>y[i]; 64 add(x[i],y[i]); 65 } 66 for (int i=1;i<=n;i++) 67 if (!dfn[i]) tarjan(i); 68 memset(g,0,sizeof(g)); 69 memset(list,0,sizeof(list)); 70 cnt=0; 71 for (int i=1;i<=m;i++) 72 if (color[x[i]]!=color[y[i]]) 73 add(color[x[i]],color[y[i]]); 74 int ans=0; 75 for (int i=1;i<=col;i++) 76 if (!f[i]) 77 { 78 dfs(i); 79 ans=max(ans,f[i]); 80 } 81 cout<<ans; 82 83 }