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

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

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : SDOI2017 天才黑客
*/
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue> using namespace std;
const int N = 5e4 + 5;
const int K = 2e4 + 5;
const int LogK = 17; int n, m, k;
struct Edge {
int to, len;
Edge(int a, int b) : to(a), len(b) { }
};
vector<Edge> adj[N << 2];
vector<int> trie[K]; int c;
void link(int u, int v, int w) { adj[u].emplace_back(v, w); ++c;}
template<int x> int get(int e) { return (e - 1) * 4 + x; }
/*1/3 out 2/4 in*/ int fa[K][LogK], dep[K], dfn[K], timer = 0;
void initLCA(int x, int f) {
dep[x] = dep[fa[x][0] = f] + 1, dfn[x] = ++timer;
for (int j = 1; j < LogK; j++) fa[x][j] = fa[fa[x][j - 1]][j - 1];
for (auto y : trie[x]) if (y != f) initLCA(y, x);
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int j = LogK - 1; ~j; --j) if (dep[fa[x][j]] >= dep[y]) x = fa[x][j];
if (x == y) return x;
for (int j = LogK - 1; ~j; --j) if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
return fa[x][0];
} vector<int> in[N];
vector<int> out[N];
int pos[N]; bool cmp1(int x, int y) { return dfn[pos[x]] < dfn[pos[y]]; }
bool cmp2(pair<int, bool> a, pair<int, bool> b) { return cmp1(a.first, b.first); }
void build(int x) {
if (in[x].empty() || out[x].empty()) return;
sort(in[x].begin(), in[x].end(), cmp1), sort(out[x].begin(), out[x].end(), cmp1); for (int i = 1; i < in[x].size(); i++) link(get<2>(in[x][i - 1]), get<2>(in[x][i]), 0);
for (int i = 1; i < in[x].size(); i++) link(get<4>(in[x][i]), get<4>(in[x][i - 1]), 0);
for (int i = 1; i < out[x].size(); i++) link(get<1>(out[x][i - 1]), get<1>(out[x][i]), 0);
for (int i = 1; i < out[x].size(); i++) link(get<3>(out[x][i]), get<3>(out[x][i - 1]), 0); vector<pair<int, bool> > rec;
for (auto v : in[x]) rec.emplace_back(v, 0);
for (auto v : out[x]) rec.emplace_back(v, 1);
sort(rec.begin(), rec.end(), cmp2); for (int t = 0, i = 0, j = 0; t < rec.size() - 1; t++) {
rec[t].second ? ++j : ++i;
int val = dep[lca(pos[rec[t].first], pos[rec[t + 1].first])] - 1;
if (i > 0 && j < out[x].size()) link(get<2>(in[x][i - 1]), get<1>(out[x][j]), val);
if (j > 0 && i < in[x].size()) link(get<4>(in[x][i]), get<3>(out[x][j - 1]), val);
}
} priority_queue< pair<long long, int>,
vector<pair<long long, int> >,
greater<pair<long long, int> > > Q;
long long dist[N << 2];
bool book[N << 2];
void dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(book, false, sizeof(book));
for (auto x : out[1]) Q.emplace(dist[get<1>(x)] = 0ll, get<1>(x));
for (auto x : out[1]) Q.emplace(dist[get<3>(x)] = 0ll, get<3>(x));
for (int x; !Q.empty(); ) {
x = Q.top().second, Q.pop();
if (book[x]) continue; book[x] = 1;
for (auto y : adj[x]) if (dist[y.to] > dist[x] + y.len)
Q.emplace(dist[y.to] = dist[x] + y.len, y.to);
}
for (int i = 2; i <= n; i++) {
long long ans = 1e18;
for (auto x : in[i]) ans = min(ans, dist[get<2>(x)]);
for (auto x : in[i]) ans = min(ans, dist[get<4>(x)]);
cout << ans << endl;
}
} void solve(){
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) in[i].clear(), out[i].clear();
for (int i = 1; i <= k; i++) trie[i].clear();
for (int i = 1; i <= m * 4; i++) adj[i].clear(); for (int i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w >> pos[i];
in[v].emplace_back(i), out[u].emplace_back(i);
link(get<1>(i), get<2>(i), w), link(get<1>(i), get<4>(i), w);
link(get<3>(i), get<2>(i), w), link(get<3>(i), get<4>(i), w);
}
for (int i = 1, u, v, w; i < k; i++) {
cin >> u >> v >> w;
trie[u].emplace_back(v);
trie[v].emplace_back(u);
} initLCA(1, 0);
for (int i = 2; i <= n; i++) build(i);
dijkstra();
}
signed main() {
ios::sync_with_stdio(false);
int T; for (cin >> T; T; --T) solve();
}

【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. linux Netfilterr中扩展match target

    Match: netfilter定义了一个通用的match数据结构struct xt_match /* 每个struct xt_match代表一个扩展match,netfilter中各个扩展match ...

  2. Ceph部署mon出现0.0.0.0地址

    前言 最近在群里两次看到出现mon地址不对的问题,都是显示0.0.0.0:0地址,如下所示: [root@lab8106 ceph]# ceph -s cluster 3137d009-e41e-41 ...

  3. JS简单介绍与简单的基本语法

    1.JavaScirpt是一门编程语言,是为前端服务的一门语言. (1)基础语法 (2)数据类型 (3)函数 (4)面向对象 2.还涉及到BOM和DOM (1)BOM(操作浏览器的一些功能) (2)D ...

  4. Spring中@Translational注解失效场景

    今天面试被问到@Translational注解什么场景下失效,我一脸懵逼,说的恍恍惚惚的,下来我就总结一下@Translational注解失效的场景! @Transactional 注解相信大家并不陌 ...

  5. 微信小程序生成二维码并且扫码跳转并且携带参数

    话不多说,直接上代码,直接把APPID和APPSECRET改成自己的就能用了 <?php header('content-type:text/html;charset=utf-8'); //配置 ...

  6. NO.A.0002——Git简史及安装教程/创建本地仓库/提交项目到本地仓库/误删还原

    一.Git简史及同类产品对比: 1.git简史:        同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代.Linux 内核开源项目有着为数众广的参与者.绝大多数的 Linu ...

  7. ccpc2020长春站F题 Strange Memory

    dsu on tree 题目链接 点我跳转 题目大意 给定一棵包含 \(n\) 个节点的树,每个节点有个权值 \(a_i\) 求\(∑_{i=1}^n∑_{j=i+1}^n[a_i⊕a_j=a_{lc ...

  8. MySQL开发篇(未完待续)

    一.索引 什么是索引? 索引是帮助Mysql提高获取数据的数据结构,换一句话讲就是"排好序的快速查找的数据结构". 1.索引的分类 MySQL主要的几种索引类型:1.普通索引.2. ...

  9. 堆的数据结构java

    public class MaxHeap { private int[] data; private int count; private int capacity; public MaxHeap(i ...

  10. yii2.0 访问控制器下的方法时出现 Object Not Found! 解决办法

    yii2.0  访问控制器下的方法时出现 Object Not Found! 时 可以查看(apache)  入口文件index.php 的同级有没有 .htaccess 文件 没有.htaccess ...