题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
思路:
显然,由于点权为正,所以位于一个强连通分量中的结点,自然走得越多答案越大
所以,我们可以跑一边tarjan,将所有的强连通分量染色,建一个新的图
其中新图上的每个结点都代表旧图上的一个强连通分量
在跑tarjan染色的同时我们可以累加得到新图上每个点的点权
最后在新图上跑一边DP或者记忆化搜索即可
代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define rii register int i #define rij register int j using namespace std; struct yb{ int from,to; }y[100005]; struct ljb{ int to,nxt; }x[100005]; int tot,dq[10005],sta[10005],sum[10005],head[10005],bnt,last[10005]; int n,m,dfn[10005],low[10005],top,cnt,vis[10005],color[10005],dp[10005]; void add(int from,int to) { bnt++; x[bnt].to=to; if(head[from]==0) { head[from]=bnt; } if(last[from]!=0) { x[last[from]].nxt=bnt; } last[from]=bnt; } void search(int wz) { if(dp[wz]!=0) { return; } dp[wz]=sum[wz]; int maxn=0; for(rii=head[wz];i!=0;i=x[i].nxt) { int ltt=x[i].to; if(dp[ltt]==0) { search(ltt); } maxn=max(maxn,dp[ltt]); } dp[wz]+=maxn; } void tarjan(int wz) { cnt++; low[wz]=cnt; dfn[wz]=cnt; top++; sta[top]=wz; vis[wz]=1; for(rii=head[wz];i!=0;i=x[i].nxt) { int ltt=x[i].to; if(dfn[ltt]==0) { tarjan(ltt); low[wz]=min(low[wz],low[ltt]); } else { if(vis[ltt]==1) { low[wz]=min(low[wz],dfn[ltt]); } } } if(dfn[wz]==low[wz]) { tot++; while(sta[top+1]!=wz) { color[sta[top]]=tot; sum[tot]+=dq[sta[top]]; vis[sta[top]]=0; top--; } } } int main() { scanf("%d%d",&n,&m); for(rii=1;i<=n;i++) { scanf("%d",&dq[i]); } for(rii=1;i<=m;i++) { scanf("%d%d",&y[i].from,&y[i].to); add(y[i].from,y[i].to); } for(rii=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } /* for(rii=1;i<=n;i++) { printf("%d ",color[i]); } cout<<endl; */ bnt=0; memset(head,0,sizeof(head)); memset(last,0,sizeof(last)); memset(x,0,sizeof(x)); for(rii=1;i<=m;i++) { if(color[y[i].from]!=color[y[i].to]) { add(color[y[i].from],color[y[i].to]); } } int ans=0; for(rii=1;i<=n;i++) { if(dp[i]==0) { search(i); } ans=max(ans,dp[i]); } printf("%d ",ans); }