【模板】缩点
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
输入输出样例
说明
n<=10^4,m<=10^5,点权<=1000
算法:Tarjan缩点+DAGdp
缩点之后建新图,记忆化搜索
#include <bits/stdc++.h> #define maxn 100005 using namespace std; struct edges { int u,v,next; }; struct m { edges edge[maxn]; int head[maxn]; int cnt=0; void init() { memset(head,-1,sizeof(head)); } void addedge(int u,int v) { edge[cnt].u=u; edge[cnt].v=v; edge[cnt].next=head[u]; head[u]=cnt++; } }M,Mp; int f[maxn],ind[maxn],dfn[maxn],low[maxn],a[maxn],in[maxn]; bool vis[maxn]; int cnt,tot,sum; stack<int> s; void Tarjan(int num) { dfn[num]=low[num]=++tot; vis[num]=true; s.push(num); for(int i=M.head[num];i!=-1;i=M.edge[i].next) { int v=M.edge[i].v; if(!dfn[v]) { Tarjan(v); low[num]=min(low[num],low[v]); } else if(vis[v]) { low[num]=min(low[num],dfn[v]); } } if(low[num]==dfn[num]) { sum++; while(true) { int now=s.top(); s.pop(); vis[now]=false; ind[now]=sum; if(now==num) break; } } } int dp[maxn]; void dfs(int num) { if(dp[num]) return; dp[num]=f[num]; int maxim=0; for(int i=Mp.head[num];i!=-1;i=Mp.edge[i].next) { int v=Mp.edge[i].v; if(!dp[v]) dfs(v); maxim=max(maxim,dp[v]); } dp[num]+=maxim; } int main() { //freopen("in.txt","r",stdin); int n,m,i,u,v; scanf("%d%d",&n,&m); M.init(); Mp.init(); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } for(int i=1;i<=m;i++) { scanf("%d%d",&u,&v); M.addedge(u,v); } for(int i=1;i<=n;i++) { if(!dfn[i]) Tarjan(i); } for(int i=1;i<=n;i++) { f[ind[i]]+=a[i]; } for(int i=1;i<=n;i++) { for(int j=M.head[i];j!=-1;j=M.edge[j].next) { v=M.edge[j].v; if(ind[i]!=ind[v]) { in[v]++; Mp.addedge(ind[i],ind[v]); } } } int ans=0; for(int i=1;i<=n;i++) { if(!dp[i]) { dfs(i); ans=max(ans,dp[i]); } } printf("%d\n",ans); return 0; }