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. TIME_WAIT状态存在的原因

    TIME_WAIT状态存在有两个理由: 1.可靠地实现TCP全双工连接的中断 2.允许老的重复分节在网络中消失 第一个理由:如果客户端的ACK丢失了,服务器将会重新发送它的最终的那个FIN,因此客户端 ...

  2. uniapp 证书 打包上线GooglePlay app自动升级

    uniapp Android证书 打包上线GooglePlay app自动升级 1.Android证书申请 要安装jdk并配置环境变量. keytool -genkey -alias android ...

  3. docker生产——容器通信

    简介 在接触docker的第一天起,大家应该就知道:docker容器使用沙箱机制,相互之间没有接口,一般情况下内部访问通过IP+端口.本地容器默认分配的IP极易发生变化,所以靠IP+端口访问的方式缺失 ...

  4. 《Machine Learning in Action》—— 小朋友,快来玩啊,决策树呦

    <Machine Learning in Action>-- 小朋友,快来玩啊,决策树呦 在上篇文章中,<Machine Learning in Action>-- Taoye ...

  5. BurpSuite多重代理的情形

    有时候为了隐藏自己的真实IP,访问目标网站需要走代理.比如说,通过代理IP访问目标网站并且代理IP可以随时切换,这样可以避免IP被封堵后无法访问目标网站的尴尬. 一. 首先把代理切换为全局模式 操作步 ...

  6. 06、MyBatis 逆向工程

    1.MyBatis逆向简介   mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java.mapper.x ...

  7. 我的开源经历:为了方便处理三方 HTTP 接口而写的 Java 框架

    缘起 我以前公司需要在 Java 后台调用许多第三方 HTTP 接口,比如微信支付.友盟等等第三方平台. 公司内部还有很多服务是用世界最好语言写的,接口自然也只能通过 HTTP 接口来调用.于是日积月 ...

  8. mongo命令行操作

  9. web安全入门--入门条件

    介绍:网络安全由习大大提出,是继海.陆.空.外太空的第五大作战领域,也是一个关系国家安全和主权.社会稳定.民族文化继承和发扬的重要问题.其重要性,正随着全球信息化步伐的加快越来越重要.接下来,我向大家 ...

  10. netsniff使用

    1 netsniff安装与使用 首先直接下载源码包进行部署 安装一些前置包(安装完成的自动忽略) sudo apt install pkg-config sudo apt install libcli ...