题目描述

给定一个 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,只不过这里找的是路径最大权值和.

那么废话就不多说了,上码

  1. #include<bits/stdc++.h>
  2. #define N 100005
  3. using namespace std;
  4. struct node
  5. {
  6. int next,to;
  7. } e[N*];//原图
  8. struct node1
  9. {
  10. int to,next;
  11. }e1[N*];//缩点后的图
  12. int head[*N],dfn[N*],low[N*];
  13. int vis[N*],color[N*],sd[N*];
  14. int w[*N],dp[N*];
  15. int head1[N*];
  16. int tim=,total,n,m,cnt,cnt1;
  17. stack<int> q;
  18. inline void add(int x,int y)
  19. {
  20. cnt++;
  21. e[cnt].to=y;
  22. e[cnt].next=head[x];
  23. head[x]=cnt;
  24. }
  25. inline void add1(int x,int y)
  26. {
  27. cnt1++;
  28. e1[cnt1].next=head1[x];
  29. e1[cnt1].to=y;
  30. head1[x]=cnt1;
  31. }
  32. void tarjan(int x)
  33. {
  34. dfn[x]=low[x]=tim;
  35. tim++;
  36. vis[x]=true;
  37. q.push(x);
  38. for(int i=head[x];i;i=e[i].next)
  39. {
  40. int u=e[i].to;
  41. if(!dfn[u])
  42. {
  43. tarjan(u);
  44. low[x]=min(low[x],low[u]);
  45. }
  46. else if(vis[u]) low[x]=min(low[x],dfn[u]);
  47. }
  48. if(low[x]==dfn[x])
  49. {
  50.  
  51. total++;
  52. int y;
  53. do{
  54. y=q.top();
  55. q.pop();
  56. vis[y]=false;
  57. color[y]=total;
  58. sd[total]+=w[y];
  59. }while(x!=y);
  60. }
  61. }
  62. int dijkstra(int x)
  63. {
  64. int sum=;
  65. memset(dp,-0x3f,sizeof(dp));
  66. memset(vis,false,sizeof(vis));
  67. queue<int> Q;
  68. Q.push(x);
  69. vis[x]=true;
  70. dp[x]=;
  71. while(!Q.empty())
  72. {
  73. x=Q.front();
  74. Q.pop();
  75. vis[x]=false;
  76. sum=max(sum,dp[x]+sd[x]);
  77. for(int i=head1[x];i;i=e1[i].next)
  78. {
  79. int y=e1[i].to;
  80. if(dp[y]<dp[x]+sd[x])
  81. dp[y]=dp[x]+sd[x];
  82. if(vis[y]) continue;
  83. Q.push(y);
  84. }
  85. }
  86. return sum;
  87. }
  88. int main()
  89. {
  90. cin>>n>>m;
  91. for(int i=;i<=n;i++)
  92. {
  93. cin>>w[i];
  94. }
  95. for(int i=;i<=m;i++)
  96. {
  97. int x,y;
  98. cin>>x>>y;
  99. add(x,y);
  100. }
  101. for(int i=;i<=n;i++)
  102. {
  103. if(!dfn[i])
  104. tarjan(i);
  105. }
  106. for(int i=;i<=n;i++)
  107. for(int j=head[i];j;j=e[j].next)
  108. {
  109. int v=e[j].to;
  110. if(color[i]!=color[v])
  111. {
  112. add1(color[i],color[v]);
  113. }
  114. }
  115. int ans=;
  116. for(int i=;i<=total;i++)
  117. {
  118. ans=max(dijkstra(i),ans);
  119. }
  120. cout<<ans;
  121. return ;
  122. }

P3387缩点(tarjan+拓扑排序+线性dp)的更多相关文章

  1. 【bzoj1093】[ZJOI2007]最大半连通子图 Tarjan+拓扑排序+dp

    题目描述 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:对于u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径. ...

  2. 【模拟7.16】通讯(tarjan缩点加拓扑排序)

    这题确实水,纯板子,考试意外出错,只拿了暴力分QAQ tarjan缩点加上拓扑排序,注意这里求最短路径时不能用最小生成树 因为是单向边,不然就可能不是一个联通图了.... 1 #include< ...

  3. 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序

    题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足:  Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆.  现在 ...

  4. 【Luogu P3387】缩点模板(强连通分量Tarjan&拓扑排序)

    Luogu P3387 强连通分量的定义如下: 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶 ...

  5. 【BZOJ-1924】所驼门王的宝藏 Tarjan缩点(+拓扑排序) + 拓扑图DP

    1924: [Sdoi2010]所驼门王的宝藏 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 787  Solved: 318[Submit][Stat ...

  6. bzoj1093[ZJOI2007]最大半连通子图(tarjan+拓扑排序+dp)

    Description 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u ...

  7. 【tarjan 拓扑排序 dp】bzoj1093: [ZJOI2007]最大半连通子图

    思维难度不大,关键考代码实现能力.一些细节还是很妙的. Description 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于 ...

  8. 洛谷P1073 Tarjan + 拓扑排序 // 构造分层图

    https://www.luogu.org/problemnew/show/P1073 C国有 n n个大城市和 mm 条道路,每条道路连接这 nn个城市中的某两个城市.任意两个城市之间最多只有一条道 ...

  9. bzoj 1093 最大半连通子图 - Tarjan - 拓扑排序 - 动态规划

    一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径.若G'=(V ...

随机推荐

  1. 简述ASP.NET Web网页的工作原理。

    ASP.NET的工作原理是:首先,有一个HTTP请求发送到Web服务器要求访问一个Web网页. Web服务器通过分析客户的HTTP请求来定位所请求网页的位置.如果所请求的网页的文件名的后缀是 aspx ...

  2. AD19覆铜与边框间距设置方法

    转载请注明出处,并附带本文网址https://www.cnblogs.com/brianblog/p/9894867.html, 由于高版本AD不能将机械层直接转变为KEPP OUT LAYER层,所 ...

  3. java byte/short/char补充(了解)

    1.在数学运算中会自动提升数据类型为 int 2.在基本赋值中,若右册的常量不超过取值范围,javac 添加 强制转换,否则报错 3.若右册 含有 变量 而不是直接使用常量相加,编译报错 例子 pub ...

  4. JS ES6补充

    补充点:1.let const 2.字符串模板 3.箭头函数  4.对象的单体模式 5.面向对象 一.定义变量 A.var 特点: 1.定义全局变量 2.可以重复定义 3.变量名提升 <!DOC ...

  5. 12.instanceof和类型转换

    Instanceof: 判断一个对象是什么类型的~,可以判断两个类之间是否存在父子关系 package com.oop.demo07; public class Person { public voi ...

  6. 解决jar包依赖冲突(idea)

    在IDEA状态下查看项目依赖的关系 关系如下图 红色数据jar包冲突 在对应的依赖中出去去冲突依赖

  7. C语言进阶——结构体,联合,枚举

    ----------------------------------------------------------我是一条划分线----------------------------------- ...

  8. ThreeJS 物理材质shader源码分析(顶点着色器)

    再此之前推荐一款GLTF物理材质在线编辑器https://tinygltf.xyz/ ThreeJS 物理材质shader源码分析(顶点着色器) Threejs将shader代码分为ShaderLib ...

  9. ios--->上下拉刷新控件MJRefresh

    上下拉刷新控件MJRefresh 一.类结构 MJRefreshComponent.h MJRefreshHeader.h MJRefreshFooter.h MJRefreshAutoFooter. ...

  10. Shell常用命令之yum

    介绍 yum命令是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器,它可以使系统管理人员交互和自动化地更细与管理RPM软件包,能够从指定的服务器自动下载RPM包并且安装,可以自动处理 ...