尽管是缩点的习题,思路也是在看了题解后才明白的。

首先,每个强连通分量内的点都是一定互通的,也就是可以完全把这里面的边都跑满,摘掉所有能摘的蘑菇。那么,考虑给每一个强连通分量化为的新点一个点权,代表摘光蘑菇能拿到的边权之和。然后,在新点之间保留原来的桥及其初始权值。(每一个桥一定只能跑一遍,否则说明这两个本应单向通行的分量之间有返回的路径,则二者可构成一个更大的分量。这个结论正是tarjan算法求有向图dcc的核心原理。)现在得到了一张新图,问题在于如何在一张包含点权、边权的DAG上求起始于定点的最长路。

这个问题可以用拓扑序DP求解。在dp求最长路的基础上,为了保证一定由s点所在分量起始,我们把该分量初状态设为其权值,其余点都赋初值为INT_MIN。

这样dp得到的最长路一定是基于f[dcc[s]]求出的。

另外,用SPFA算法来跑点权、边权交叉的最长路是可行的,不过应用于本题复杂度不如dp优秀。-----------------------------------------

在参阅题解后,基于一开始跑偏的假设,笔者又想到了一个貌似更优的解法。

实际上我们并不需要考虑原图的所有节点。容易想到,从给定起点向外作一轮tarjan算法(dfs)不能达到的点,在新图中也不可能走到。因此,我们只需要对图中以s为原点作一次tarjan能够跑到的几个连通分量进行缩点,这样能够到达的区域就变成了一棵以s为根的树(8月20日订正:这里的“树”更严谨的说法是“树形图”)。我们只需要再作一次dfs求出最深叶节点的深度即可。

(注:以下代码注释分部分为除最后一种思路的其余解法,仅供参考)

  1. #include <cstdio>
  2. #include <iostream>
  3. #include <queue>
  4. #include <climits>
  5. #define rint register int
  6. #define BUG putchar('*')
  7. #define maxn 80010
  8. #define maxm 200010
  9. using namespace std;
  10. struct E {
  11. int to, nxt, w;
  12. double op;
  13. } edge[maxm], edge2[maxm];
  14. int n, m, st;
  15. int head[maxn], top;
  16. inline void insert(int u, int v, int w, double op) {
  17. edge[++top] = (E) {v, head[u], w, op};
  18. head[u] = top;
  19. }
  20. int dfn[maxn], low[maxn], sta[maxn], stp, timer;
  21. bool ins[maxn], vis[maxn];
  22. int cnt, c[maxn];
  23. void dfs(int u) {
  24. dfn[u] = low[u] = ++timer;
  25. sta[++stp] = u;
  26. ins[u] = true;
  27. vis[u] = true;//  仅搜一次标记所答点
  28. for (rint i = head[u]; i; i = edge[i].nxt) {
  29. int v = edge[i].to;
  30. if (!dfn[v]) {
  31. dfs(v);
  32. low[u] = min(low[u], low[v]);
  33. } else if (ins[v])
  34. low[u] = min(low[u], dfn[v]);
  35. }
  36. if (dfn[u] == low[u]) {
  37. ++cnt;
  38. int x;
  39. do {
  40. x = sta[stp--];
  41. ins[x] = false;
  42. c[x] = cnt;
  43. } while (x != u);
  44. }
  45. }
  46. void tarjan() {
  47. //  for (int i = 1; i <= n; ++i) //  全图tarjan
  48. //      if (!dfn[i]) dfs(i);
  49. dfs(st);
  50. }
  51. int head2[maxn], top2;
  52. inline void insert2(int u, int v, int w) {
  53. edge2[++top2] = (E) {v, head2[u], w, 0};
  54. head2[u] = top2;
  55. }
  56. int val[maxn], ind[maxn];
  57. void build() {
  58. rint v, w;
  59. for (rint u = 1; u <= n; ++u)
  60. if (vis[u])//  仅考虑一次搜索 缩点得树
  61. for (int i = head[u]; i; i = edge[i].nxt) {
  62. v = edge[i].to;
  63. w = edge[i].w;
  64. if (c[u] == c[v]) {
  65. register double op = edge[i].op;
  66. while (w)
  67. val[c[u]] += w, w *= op;
  68. } else
  69. insert2(c[u], c[v], w), ind[c[v]]++;
  70. }
  71. }
  72. //************************
  73. /*  DAG 拓扑序dp
  74. int f[maxn];
  75. queue<int> q;
  76. int dp() {
  77. int ans = val[c[st]];
  78. for (int i = 1; i <= cnt; ++i) {
  79. f[i] = INT_MIN;
  80. if (!ind[i]) q.push(i);
  81. }
  82. f[c[st]] = val[c[st]];
  83. while (!q.empty()) {
  84. int u = q.front(); q.pop();
  85. for (int i = head2[u]; i; i = edge2[i].nxt) {
  86. int v = edge2[i].to;
  87. f[v] = max(f[v], f[u] + edge2[i].w + val[v]);
  88. --ind[v];
  89. if (!ind[v])
  90. ans = max(ans, f[v]), q.push(v);
  91. }
  92. }
  93. return ans;
  94. }
  95. */
  96. //**************************
  97. /*  spfa
  98. bool inq[maxn];
  99. int dist[maxn];
  100. int spfa() {
  101. for (int i = 1; i <= cnt; ++i)
  102. dist[i] = INT_MIN;
  103. dist[c[st]] = val[c[st]];
  104. queue<int> q;
  105. inq[c[st]] = true, q.push(c[st]);
  106. while (!q.empty()) {
  107. int u = q.front();
  108. q.pop(), inq[u] = false;
  109. for (int i = head2[u]; i; i = edge2[i].nxt) {
  110. int v = edge2[i].to;
  111. if (dist[v] < dist[u] + edge2[i].w + val[v]) {
  112. dist[v] = dist[u] + edge2[i].w + val[v];
  113. if (!inq[v])
  114. q.push(v), inq[v] = true;
  115. }
  116. }
  117. }
  118. int ans = 0;
  119. for (int i = 1; i <= cnt; ++i)
  120. ans = max(ans, dist[i]);
  121. return ans;
  122. }*/
  123. //***************************
  124. int ans;
  125. void dfs2(int u, int dist) {
  126. dist += val[u];
  127. if (!head2[u]) {
  128. ans = max(ans, dist);
  129. return;
  130. }
  131. for (int i = head2[u]; i; i = edge2[i].nxt)
  132. dfs2(edge2[i].to, dist + edge2[i].w);
  133. }
  134. int main() {
  135. scanf("%d %d", &n, &m);
  136. int u, v, w;
  137. double op;
  138. for (rint i = 1; i <= m; ++i) {
  139. scanf("%d %d %d %lf", &u, &v, &w, &op);
  140. insert(u, v, w, op);
  141. }
  142. scanf("%d", &st);
  143. tarjan();
  144. build();
  145. //  printf("%d", spfa());
  146. //  printf("%d", dp());
  147. dfs2(c[st], 0);
  148. printf("%d", ans);
  149. return 0;
  150. }

这个题最大的收获是发现有向图缩点总跟DAG上的topo+DP有联系。按拓扑序遍历到某一点u,意味u点所有的入点都已经对其完成了更新,此时u点的状态满足无后效性。以及在DAG上求解始于某点的最长路径时,对f数组的特殊处理。

Luogu P2656 采蘑菇的更多相关文章

  1. 洛谷——P2656 采蘑菇

    P2656 采蘑菇 题目描述 小胖和ZYR要去ESQMS森林采蘑菇. ESQMS森林间有N个小树丛,M条小径,每条小径都是单向的,连接两个小树丛,上面都有一定数量的蘑菇.小胖和ZYR经过某条小径一次, ...

  2. 洛谷—— P2656 采蘑菇

    https://www.luogu.org/problem/show?pid=2656 题目描述 小胖和ZYR要去ESQMS森林采蘑菇. ESQMS森林间有N个小树丛,M条小径,每条小径都是单向的,连 ...

  3. [Luogu 2656] 采蘑菇

    Description 小胖和ZYR要去ESQMS森林采蘑菇. ESQMS森林间有N个小树丛,M条小径,每条小径都是单向的,连接两个小树丛,上面都有一定数量的蘑菇.小胖和ZYR经过某条小径一次,可以采 ...

  4. 洛谷 P2656 采蘑菇 树形DP+缩点+坑点

    题目链接 https://www.luogu.com.cn/problem/P2656 分析 这其实是个一眼题(bushi 发现如果没有那个恢复系数,缩个点就完了,有恢复系数呢?你发现这个恢复系数其实 ...

  5. 【Foreign】采蘑菇 [点分治]

    采蘑菇 Time Limit: 20 Sec  Memory Limit: 256 MB Description Input Output Sample Input 5 1 2 3 2 3 1 2 1 ...

  6. 【细节题 离线 树状数组】luoguP4919 Marisa采蘑菇

    歧义差评:但是和题意理解一样了之后细节依然处理了很久,说明还是水平不够…… 题目描述 Marisa来到了森林之中,看到了一排nn个五颜六色的蘑菇,编号从1-n1−n,这些蘑菇的颜色分别为col[1], ...

  7. luogu P2056 采花

    题目描述 萧芸斓是 Z国的公主,平时的一大爱好是采花. 今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花.花园足够大,容纳了 n 朵花,花有 c 种颜色(用整数 1-c 表示) ,且花是排成 ...

  8. F 采蘑菇的克拉莉丝

    这是一道树链剖分的题目: 很容易想到,我们在树剖后,对于操作1,直接单点修改: 对于答案查询,我们直接的时候,我们假设查询的点是3,那么我们在查询的时候可分为两部分: 第一部分:查找出除3这颗子树以外 ...

  9. [Luogu1119]采蘑菇

    题目大意: 给你一个无向图,点i在时间t[i]之前是不存在的,有q组询问,问你时间为t时从x到y的最短路. 点的编号按出现的时间顺序给出,询问也按照时间顺序给出. 思路: Floyd. Floyd的本 ...

随机推荐

  1. c语言创建dll以及使用

    0x01. declspec(dllexport)创建导出dll 笔者这边使用vs 2015,但是性质都一样的 新建项目 -> Win32控制台应用程序 -> dll 这时候就创建了一个项 ...

  2. 删除指定路径下指定天数之前(以文件的修改日期为准)的文件:forfiles

    删除指定路径下指定天数之前(以文件的修改日期为准)的文件:forfiles 代码如下: @echo off ::演示:删除指定路径下指定天数之前(以文件的最后修改日期为准)的文件. ::如果演示结果无 ...

  3. 电脑查看当前自己的wifi密码

    菜单+R 输入control  点击确认.

  4. Mybatis的基础配置

    mybatis相关配置 mybatis核心文件配置的用法以及事例(初级) properties标签:获取外部的配置文件 <properties resource="jdbc.prope ...

  5. 【Kata Daily 190918】Spacify(插空)

    题目: Modify the spacify function so that it returns the given string with spaces insertedbetween each ...

  6. sublime中插件

    AllAutocomplete Emmet

  7. 18、Celery

    Celery 1.什么是Clelery Celery是一个简单.灵活且可靠的,处理大量消息的分布式系统 专注于实时处理的异步任务队列 同时也支持任务调度 Celery架构 Celery的架构由三部分组 ...

  8. Prometheus监控告警浅析

    前言 最近有个新项目需要搞一套完整的监控告警系统,我们使用了开源监控告警系统Prometheus:其功能强大,可以很方便对其进行扩展,并且可以安装和使用简单:本文首先介绍Prometheus的整个监控 ...

  9. centos常用指令之-卸载

    卸载centos自带java: rpm -qa|grep java // 查询javax相关 xxxxxxxxxxxxxx # 卸载1.2方式 # 1 yum -y remove java xxxxx ...

  10. animation关键帧动画语法

    基本声明和用法 @-webkit-keyframes NAME-YOUR-ANIMATION { 0% { opacity: 0; } 100% { opacity: 1; } } @-moz-key ...