Description

给定一张有向图,\(n\) 个点,\(m\) 条边。第 \(i\) 条边上有一个边权 \(c_i\),以及一个字符串 \(s_i\)。

其中字符串 \(s_1, s_2, \cdots , s_m\) 组成的字典树的结点数为 \(k\)。字典树在输入时给出,每个字符串 \(s_i\) 以一个正整数 \(d_i\) 的形式给出,表示 \(s_i\) 对应字典树上的 \(d_i\) 号结点。

若一条路径经过的边依次为 \(e_1, e_2,\cdots, e_p\),那么路径的长度定义为 \(\sum_{i=1}^p c_{e_i} + \sum_{i=1}^{p-1}\operatorname{lcp}(s_{e_i}, s_{e_{i+1}})\)。其中 \(\operatorname{lcp}(s, t)\) 表示字符串 \(s, t\) 最长公共前缀的长度。

求顶点 \(1\) 开始的单源最短路径。共 \(T(\le 10)\) 组数据。

Hint

\(1\le n, m\le 5\times 10^4, 1\le k, c_i\le 2\times 10^4\)

Solution

下面复杂度中 \(n, m,k\) 同阶

观察题目,发现路径长的计算涉及到相邻两条边的 \(\text{lcp}\),即使直接暴力跑 Dijkstra 也非常不方便,不如将这个图乱搞一波,最好跑最短路时可以直接上板子。那么考虑怎样优秀地建图。

考虑把边变成虚点,然后试着把 \(\text{lcp}\) 部分以 虚点间再连虚边 的方式,达到转化掉这个“相邻”的目的。具体地,假如说存在两条边 \(u\to v, v\to w\),边权分别为 \(c_i, c_j\),字符串分别为 \(s_i, s_j\),那么我们建立虚点 \(t(i), t(j)\),点权分别为 \(c_i, c_j\),并有一条 \(t(i)\to t(j)\) 的边且边权为 \(\text{lcp}(s_i, s_j)\)。\(\text{lcp}\) 即为给定 Trie 树上的 LCA 的深度。

但这样还是不太舒服,我们再把点权化掉:我们对一条边建 两个虚点 而不是一个,分别作为入点和出点。对于第 \(i\) 条边的入点和出点分别记做 \(t_{in}(i), t_{out}(i)\)。那么原先的点权我们可以放到 \(t_{in}(i)\to t_{out}(i)\) 这条边上。

如果把图建出来了,那么是可以套 Dijkstra 板子的。不过如果有一个菊花图,那么会存在 \(O(m^2)\) 条虚边,时空两爆炸。


考虑这样一个问题,对于 Trie 上的结点 \(r_1, r_2, \cdots, r_R\),我们要求所有结点间的 LCA 的深度。如果我们将 \(r\) 先按 Trie 的 DFS 序 排序,那么可以发现:\(\text{depth}(\text{LCA}(r_i, r_j)) = \min_{i\le k<j}\{\text{depth}(\text{LCA}(r_k, r_{k+1}))\}\)。也就是说,我们只要求出相邻两个的 LCA,其他都可以用 区间最小值 表示。

把这个思路套到这道题上,我们将一个点 \(x\) 的所有出边的入点,所有入边的出点(就是 \(x\) 周围一圈),分别按 Trie 的 DFS 序排序,然后我们发现这是一个 点向区间,区间向点 连边的问题,于是维护两颗线段树来优化连边即可,这样总边数只有 \(O(n\log n)\) 条。

现在我们得到了一个 \(O(n\log^2 n)\) 时间的算法,写的好理论上就能过了。


其实还可以优化,我们其实不需要线段树。

如果出点、入点排序后的结果为 \(\{a_1, a_2, \cdots, a_A\}, \{b_1, b_2, \cdots, b_B\}\),假如 \(a_i \to b_j\) 的虚边对应 \(\text{lcp}\) 大小为 \(l\),那么 出点 \(a_1, a_2, \cdots , a_i\) 都能以不超过 \(l\) 的代价到达出点 \(b_j, b_{j+1}, \cdots ,b_B\)。那么本质上是一段 前缀 向一段 后缀 连边。

于是我们就可以:

连接 \(a_i, a_{i+1}\) 和 \(b_i, b_{i+1}\) 的边权我们设为 \(0\)。这样就只需要计算 DFS 序相邻的就行了,总边数被控制在了 \(O(m)\)。

但是这样只能处理 \(i\le j\) 时 \(a_i\to b_j\) 的情况,如果 \(i>j\) 是不是无法处理呢?

显然可以,我们只要一开始将一条边拆成 \(4\) 个虚点,两个入点,两个出点,每个入点都想两个出点连边。然后对于其中一对出入点我们如上述建边,然后另外一对我们只要把上图的 \(0\) 边 反着建 即可。

这就是所谓的 前后缀优化建图

加上 Dijkstra 的复杂度这题的时间为 \(O(n\log n)\)。

Code

建图是本题的精髓,建议自己在纸上画一画。

  1. /*
  2. * Author : _Wallace_
  3. * Source : https://www.cnblogs.com/-Wallace-/
  4. * Problem : SDOI2017 天才黑客
  5. */
  6. #include <algorithm>
  7. #include <cstring>
  8. #include <iostream>
  9. #include <vector>
  10. #include <queue>
  11. using namespace std;
  12. const int N = 5e4 + 5;
  13. const int K = 2e4 + 5;
  14. const int LogK = 17;
  15. int n, m, k;
  16. struct Edge {
  17. int to, len;
  18. Edge(int a, int b) : to(a), len(b) { }
  19. };
  20. vector<Edge> adj[N << 2];
  21. vector<int> trie[K];
  22. int c;
  23. void link(int u, int v, int w) { adj[u].emplace_back(v, w); ++c;}
  24. template<int x> int get(int e) { return (e - 1) * 4 + x; }
  25. /*1/3 out 2/4 in*/
  26. int fa[K][LogK], dep[K], dfn[K], timer = 0;
  27. void initLCA(int x, int f) {
  28. dep[x] = dep[fa[x][0] = f] + 1, dfn[x] = ++timer;
  29. for (int j = 1; j < LogK; j++) fa[x][j] = fa[fa[x][j - 1]][j - 1];
  30. for (auto y : trie[x]) if (y != f) initLCA(y, x);
  31. }
  32. int lca(int x, int y) {
  33. if (dep[x] < dep[y]) swap(x, y);
  34. for (int j = LogK - 1; ~j; --j) if (dep[fa[x][j]] >= dep[y]) x = fa[x][j];
  35. if (x == y) return x;
  36. for (int j = LogK - 1; ~j; --j) if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
  37. return fa[x][0];
  38. }
  39. vector<int> in[N];
  40. vector<int> out[N];
  41. int pos[N];
  42. bool cmp1(int x, int y) { return dfn[pos[x]] < dfn[pos[y]]; }
  43. bool cmp2(pair<int, bool> a, pair<int, bool> b) { return cmp1(a.first, b.first); }
  44. void build(int x) {
  45. if (in[x].empty() || out[x].empty()) return;
  46. sort(in[x].begin(), in[x].end(), cmp1), sort(out[x].begin(), out[x].end(), cmp1);
  47. for (int i = 1; i < in[x].size(); i++) link(get<2>(in[x][i - 1]), get<2>(in[x][i]), 0);
  48. for (int i = 1; i < in[x].size(); i++) link(get<4>(in[x][i]), get<4>(in[x][i - 1]), 0);
  49. for (int i = 1; i < out[x].size(); i++) link(get<1>(out[x][i - 1]), get<1>(out[x][i]), 0);
  50. for (int i = 1; i < out[x].size(); i++) link(get<3>(out[x][i]), get<3>(out[x][i - 1]), 0);
  51. vector<pair<int, bool> > rec;
  52. for (auto v : in[x]) rec.emplace_back(v, 0);
  53. for (auto v : out[x]) rec.emplace_back(v, 1);
  54. sort(rec.begin(), rec.end(), cmp2);
  55. for (int t = 0, i = 0, j = 0; t < rec.size() - 1; t++) {
  56. rec[t].second ? ++j : ++i;
  57. int val = dep[lca(pos[rec[t].first], pos[rec[t + 1].first])] - 1;
  58. if (i > 0 && j < out[x].size()) link(get<2>(in[x][i - 1]), get<1>(out[x][j]), val);
  59. if (j > 0 && i < in[x].size()) link(get<4>(in[x][i]), get<3>(out[x][j - 1]), val);
  60. }
  61. }
  62. priority_queue< pair<long long, int>,
  63. vector<pair<long long, int> >,
  64. greater<pair<long long, int> > > Q;
  65. long long dist[N << 2];
  66. bool book[N << 2];
  67. void dijkstra() {
  68. memset(dist, 0x3f, sizeof(dist));
  69. memset(book, false, sizeof(book));
  70. for (auto x : out[1]) Q.emplace(dist[get<1>(x)] = 0ll, get<1>(x));
  71. for (auto x : out[1]) Q.emplace(dist[get<3>(x)] = 0ll, get<3>(x));
  72. for (int x; !Q.empty(); ) {
  73. x = Q.top().second, Q.pop();
  74. if (book[x]) continue; book[x] = 1;
  75. for (auto y : adj[x]) if (dist[y.to] > dist[x] + y.len)
  76. Q.emplace(dist[y.to] = dist[x] + y.len, y.to);
  77. }
  78. for (int i = 2; i <= n; i++) {
  79. long long ans = 1e18;
  80. for (auto x : in[i]) ans = min(ans, dist[get<2>(x)]);
  81. for (auto x : in[i]) ans = min(ans, dist[get<4>(x)]);
  82. cout << ans << endl;
  83. }
  84. }
  85. void solve(){
  86. cin >> n >> m >> k;
  87. for (int i = 1; i <= n; i++) in[i].clear(), out[i].clear();
  88. for (int i = 1; i <= k; i++) trie[i].clear();
  89. for (int i = 1; i <= m * 4; i++) adj[i].clear();
  90. for (int i = 1, u, v, w; i <= m; i++) {
  91. cin >> u >> v >> w >> pos[i];
  92. in[v].emplace_back(i), out[u].emplace_back(i);
  93. link(get<1>(i), get<2>(i), w), link(get<1>(i), get<4>(i), w);
  94. link(get<3>(i), get<2>(i), w), link(get<3>(i), get<4>(i), w);
  95. }
  96. for (int i = 1, u, v, w; i < k; i++) {
  97. cin >> u >> v >> w;
  98. trie[u].emplace_back(v);
  99. trie[v].emplace_back(u);
  100. }
  101. initLCA(1, 0);
  102. for (int i = 2; i <= n; i++) build(i);
  103. dijkstra();
  104. }
  105. signed main() {
  106. ios::sync_with_stdio(false);
  107. int T; for (cin >> T; T; --T) solve();
  108. }

【SDOI2017】天才黑客(前后缀优化建图 & 最短路)的更多相关文章

  1. 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)

    题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...

  2. Codeforces 587D - Duff in Mafia(2-SAT+前后缀优化建图)

    Codeforces 题面传送门 & 洛谷题面传送门 2-SAT hot tea. 首先一眼二分答案,我们二分答案 \(mid\),那么问题转化为,是否存在一个所有边权都 \(\le mid\ ...

  3. 洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)

    题面传送门 神仙题一道. 首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法--边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,ou ...

  4. G. 神圣的 F2 连接着我们 线段树优化建图+最短路

    这个题目和之前写的一个线段树优化建图是一样的. B - Legacy CodeForces - 787D 线段树优化建图+dij最短路 基本套路 之前这个题目可以相当于一个模板,直接套用就可以了. 不 ...

  5. CodeForces 786B Legacy(线段树优化建图+最短路)

    [题目链接] http://codeforces.com/problemset/problem/786/B [题目大意] 给出一些星球,现在有一些传送枪,可以从一个星球到另一个星球, 从一个星球到另一 ...

  6. Codeforces.786B.Legacy(线段树优化建图 最短路Dijkstra)

    题目链接 \(Description\) 有\(n\)个点.你有\(Q\)种项目可以选择(边都是有向边,每次给定\(t,u,v/lr,w\)): t==1,建一条\(u\to v\)的边,花费\(w\ ...

  7. [SDOI2017]天才黑客[最短路、前缀优化建图]

    题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...

  8. Luogu P3783 [SDOI2017]天才黑客

    题目大意 一道码量直逼猪国杀的图论+数据结构题.我猪国杀也就一百来行 首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息.让你找一个点出发到其它点的最短路径.听起来很简单 ...

  9. 洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)

    成功又一次自闭了 怕不是猪国杀之后最自闭的一次 一看到最短路径. 我们就能推测这应该是个最短路题 现在考虑怎么建图 根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并 ...

随机推荐

  1. 1. 线性DP 120. 三角形最小路径和

    经典问题: 120. 三角形最小路径和  https://leetcode-cn.com/problems/triangle/ func minimumTotal(triangle [][]int) ...

  2. linux配置yum源、mount及yum命令

    配置yum源:  在/mnt目录下新建一个空的目录,名为rhel. [root@localhost mnt]# mkdir rhel 然后 [root@localhost Packages]# cd ...

  3. Python_多进程_pool进程池

    多进程典型案例: 1.将子进程的进程名作为列表中的元素,在父进程中遍历(异步)执行 #coding: utf-8 from multiprocessing import Pool import os, ...

  4. SQL SERVER数据库内 FOR XML PATH 函数用法

    把自己点点滴滴的学习记录下来!!!! 一.FOR XML PATH 简单介绍 那么还是首先来介绍一下FOR XML PATH ,假设现在有一张兴趣爱好表(TBJTXXCE)用来存放就诊患者信息,表结构 ...

  5. 多线程实现socketserver练习

    1.server import socket from threading import Thread def my_socketserver(conn, addr): conn.send(b'hel ...

  6. 如何在Guitar Pro上添加吉他和弦

    Guitar Pro是一款很适合广大吉他爱好者的优秀吉他谱学习与制谱软件,吉他爱好者可以使用它来更好的辅助自己学习吉他.在我们根据弹唱时,都会跟着谱子上标记的和弦来弹奏,不同的和弦有着不同的风格,或暗 ...

  7. JUC并发工具包之CyclicBarrier & CountDownLatch的异同

    1.介绍 本文我们将比较一下CyclicBarrier和CountDownLatch并了解两者的相似与不同. 2.两者是什么 当谈到并发,将这两者概念化的去解释两者是做什么的,这其实是一件很有挑战的事 ...

  8. dubbo 多注册中心

    这个我调试了下,多个注册中心在创建代理的时候,每个注册中心对应一个invoker,持有一个RegistryDirectory对应一个zkClinet,并且维护这样一个map: 那些不正确zk在创建代理 ...

  9. 冲刺随笔——Day_Two

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺 作业正文 正文 其他参考文献 无 ...

  10. 小bug小坑总结

    1. 小程序canvas那些原生组件的层级默认是最高的,而且不能更改,平常的div弹框什么的上面就会显示出原生组件的内容, 解决办法:cover-view,cover-image,button 2. ...