缩点tarjan
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
缩点含义:将一个环缩成一个点,然后把原本环上的点与外界相连的边,接到这个点上面
换句话讲就是 tarjan求出的所有强连通分量都变成点,这样有向有环图就变成有向无环图(DAG),化简了问题
对于这题,因为可以重复走边且只计算一次,那么如果有环的话为何不走,既然走了,那么这个环本身对答案其实就无意义了,所以用缩点
缩点做法:
工具:tarjan的dfn,low,stack,dfs
思想:stack栈回溯的时候,环中点的权值都加到最先遍历的点上
int tim=0; int color_num=0; void tarjan(int u){ dfn[u]=low[u]=++tim; s[++top]=u; vis[u]=1; for(i,fi[u],nx){ int v=e[i].to; if(!dfn[v])tarjan(v),chkmin(low[u],low[v]); else if(vis[v])chkmin(low[u],dfn[v]); } //回溯 if(low[u]==dfn[u]){ color_num++; while(s[top+1]!=u){//就是这里top+1 color[s[top]]=color_num; sum[color_num]+=val[s[top]];//就这一步回加 vis[s[top--]=false;//这里放top-- }}} //回溯这么写也很优秀,反正u最后处理 if(low[u]==dfn[u]){ int v; while(v=s[top--]){ point[v]=u;//指向u vis[v]=false; if(u==v)break; sum[u]+=sum[v]; }} For(i,1,n) if(!dfn[i])tarjan(i,i);
下面处理DAG中计算最大权值问题
两种思路:
1.记忆化dfs
2.拓扑排序(针对存在后效性dp的手段)
记得重新建DAG图
法一:
void dfs(int u){ if(f[u])return; int res=0; for(i,fi[u],nx){ int v=e[i].to; dfs(v); chkmax(res,f[v]); } f[u]+=res; } For(i,1,m) if(color[e[i].to]!=color[e[i].from]) add(color[e[i].to],color[e[i].from]); For(i,1,color_num) if(!f[i])dfs(i),chkmax(ans,f[i]);
法二:
void topo(){ queue<int>q; For(i,1,n) if(in[i]==0&&point[i]==i)q.push(i),f[i]=sum[i]; while(!q.empty()){ int u=q.front();q.pop(); for(i,fi[u],nx){ int v=e[i].to; in[v]--; chkmax(f[v],f[u]+sum[v]); if(!in[v])q.push(v); }} For(i,1,n) chkmax(ans,f[i]); }