P3387缩点(tarjan+拓扑排序+线性dp)
题目描述
给定一个 n个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n,,m
第二行 n 个整数,依次代表点权
第三至 m+2行,每行两个整数 u,v,表示一条 u→v 的有向边。
输出格式
共一行,最大的点权之和。
题目链接:P3387
分析:相信大家已经看过题目了,在看我的题解之前也应该看过别的大佬的题解了(毕竟我是蒟蒻),虽然所有的题解一上来就会明确告诉你思路是tarjan+拓扑+dp,但为什么是这个思路确很少分析,我们不妨来分析一下加深自己的理解
对于这道题,要找出加权有向图中一条路径上最大的权值和sum(w),w我们第一时间想到是什么情况下sum(w)最大,第一反应是路径越长则sum(w)趋于越大,虽然极其的不严格,但却好比一根团绳子(一个图),表面上很难看出来哪一根最长,因为有折叠起来的部分(环),所以第一步要拆折叠为直线(tarjan缩点),在缩点后,有向图中的环消失,原图中的环被缩成了点.
好,现在折叠的部分被拆开了,图现在变成了有向无环图,路径清晰明确,是时候进行dp了,用dp来找出最优解(类似于最长上升子序列,在选与不选中抉择),但是想要进行dp,还要满足以下两个条件:
1.最优子结构.
2.无后效性.
对于最优子结构:我们规定,dp[i]为当dp到点i时的最优情况,那么dp[n]为最终的唯一解,设最后一次选取在点u->n之间,那么,要是dp[n]最大,有dp[n]=max(dp[n],dp[u]+w[n]);
证明:假设dp[u]不符合最优解,那么则必定存在有dp1[u]>dp[u],那么有dp1[n]=dp1[u]+w[v]>dp[u],这与dp[n]的最优性矛盾,因为在n时的dp[n]已经是唯一最优解.
然而对于无后效性,在某次dp时因为dp此时的图并不是严格线性的,合并前的状态有可能影响合并后的走向,比如遇到多出度的点,无法保证无后效性
那么这时就要用到拓扑排序,我个人的理解为,将一个有向无环图变为一个线性序列,在这个线性序列中,取任意两点u,v,如果从u->v出现在缩点后图的传递闭包中,那么在队列中u必然在v之前.
这时,dp变成了线性dp,无后效性轻松保证.
具体的算法实现
好,现在我们知道为什么要用这三样东西了,思路就变得清晰起来.首先对于tarjan,如果是从受欢迎的牛或者从其他强连通分量的题过来的小伙伴应该大致有一些了解.
在tarjan的过程中主要是动态的更新两个东西,dfn和low,代表着按时间上遍历到的顺序情况和够追溯到的最早的栈中节点的次序号,当dfn[u]==low[u]时,即某一次dfn时又指回了low[u],所以可以成环.
这里安利一下我的另一篇题解:P1262间谍网络
对于拓扑排序我一般写bfs(其他方法记不住,因为比较菜),具体思路为,找入度为0的点加入队列,然后删除该点的所有出度边,在次寻找,重复下去直至空图,类似于dijkstra,只不过这里找的是路径最大权值和.
那么废话就不多说了,上码
#include<bits/stdc++.h> #define N 100005 using namespace std; struct node { int next,to; } e[N*5];//原图 struct node1 { int to,next; }e1[N*5];//缩点后的图 int head[5*N],dfn[N*5],low[N*5]; int vis[N*5],color[N*5],sd[N*5]; int w[5*N],dp[N*5]; int head1[N*5]; int tim=1,total,n,m,cnt,cnt1; stack<int> q; inline void add(int x,int y) { cnt++; e[cnt].to=y; e[cnt].next=head[x]; head[x]=cnt; } inline void add1(int x,int y) { cnt1++; e1[cnt1].next=head1[x]; e1[cnt1].to=y; head1[x]=cnt1; } void tarjan(int x) { dfn[x]=low[x]=tim; tim++; vis[x]=true; q.push(x); for(int i=head[x];i;i=e[i].next) { int u=e[i].to; if(!dfn[u]) { tarjan(u); low[x]=min(low[x],low[u]); } else if(vis[u]) low[x]=min(low[x],dfn[u]); } if(low[x]==dfn[x]) { total++; int y; do{ y=q.top(); q.pop(); vis[y]=false; color[y]=total; sd[total]+=w[y]; }while(x!=y); } } int dijkstra(int x) { int sum=0; memset(dp,-0x3f,sizeof(dp)); memset(vis,false,sizeof(vis)); queue<int> Q; Q.push(x); vis[x]=true; dp[x]=0; while(!Q.empty()) { x=Q.front(); Q.pop(); vis[x]=false; sum=max(sum,dp[x]+sd[x]); for(int i=head1[x];i;i=e1[i].next) { int y=e1[i].to; if(dp[y]<dp[x]+sd[x]) dp[y]=dp[x]+sd[x]; if(vis[y]) continue; Q.push(y); } } return sum; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { cin>>w[i]; } for(int i=1;i<=m;i++) { int x,y; cin>>x>>y; add(x,y); } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } for(int i=1;i<=n;i++) for(int j=head[i];j;j=e[j].next) { int v=e[j].to; if(color[i]!=color[v]) { add1(color[i],color[v]); } } int ans=0; for(int i=0;i<=total;i++) { ans=max(dijkstra(i),ans); } cout<<ans; return 0; }