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. select模型(一 改进客户端)

    一.改程序使用select来改进客户端对标准输入和套接字输入的处理,否则关闭服务器之后循环中的内容都要被gets阻塞.原程序中https://www.cnblogs.com/wsw-seu/p/841 ...

  2. LOJ #2005. 「SDOI2017」相关分析 线段树维护回归直线方程

    题目描述 \(Frank\) 对天文学非常感兴趣,他经常用望远镜看星星,同时记录下它们的信息,比如亮度.颜色等等,进而估算出星星的距离,半径等等. \(Frank\) 不仅喜欢观测,还喜欢分析观测到的 ...

  3. transformer多头注意力的不同框架实现(tensorflow+pytorch)

    多头注意力可以用以下一张图描述: 1.使用pytorch自带的库的实现 torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, b ...

  4. maven 笔记2

    maven 中央工厂的位置:D:\dubbo\apache-maven-3.2.5\lib D:\dubbo\apache-maven-3.2.5\lib pom-4.0.0.xml reposito ...

  5. .net core quartz job作业调度管理组件

    定时作业对于多数系统来说,都会用到,定时作业的实现方式可以有多种方式,简单的方式用Timer就可以实现,但是达不到通用的效果,本文采用Quartz基础组件来搭建一套企业通用的作业调度管理服务,希望对于 ...

  6. 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!

    Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...

  7. CLH lock queue的原理解释及Java实现

    目录 背景 原理解释 Java代码实现 定义QNode 定义Lock接口 定义CLHLock 使用场景 运行代码 代码输出 代码解释 CLHLock的加锁.释放锁过程 第一个使用CLHLock的线程自 ...

  8. elasticsearch 使用同义词

    elasticsearch 使用同义词 使用环境 elasticsearch5.1.1 kibana5.1.1 同义词插件5.1.1 安装插件 下载对应的elasticsearch-analysis- ...

  9. Java IDEA 根据mybatis-generator-core自动生成代码支持sqlserver获取备注(二)

    mybatis generator代码生成虽然好用,但是好像不支持sqlserver获取备注信息,这里我主要分享mybatis generator改写后支持sqlserver获取备注信息,mysql以 ...

  10. 简化的鸿蒙WiFi接口,仅需几行代码,简单易用!

    使用鸿蒙原始WiFI API接口进行编程,整个过程稍显繁琐,为此我们对鸿蒙原始WiFi API接口做了一层封装,形成了一套更简单易用的接口. 简化后的API接口 STA模式 // 连接WiFi热点,并 ...