【SDOI2017】天才黑客(前后缀优化建图 & 最短路)
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】天才黑客(前后缀优化建图 & 最短路)的更多相关文章
- 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...
- Codeforces 587D - Duff in Mafia(2-SAT+前后缀优化建图)
Codeforces 题面传送门 & 洛谷题面传送门 2-SAT hot tea. 首先一眼二分答案,我们二分答案 \(mid\),那么问题转化为,是否存在一个所有边权都 \(\le mid\ ...
- 洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)
题面传送门 神仙题一道. 首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法--边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,ou ...
- G. 神圣的 F2 连接着我们 线段树优化建图+最短路
这个题目和之前写的一个线段树优化建图是一样的. B - Legacy CodeForces - 787D 线段树优化建图+dij最短路 基本套路 之前这个题目可以相当于一个模板,直接套用就可以了. 不 ...
- CodeForces 786B Legacy(线段树优化建图+最短路)
[题目链接] http://codeforces.com/problemset/problem/786/B [题目大意] 给出一些星球,现在有一些传送枪,可以从一个星球到另一个星球, 从一个星球到另一 ...
- Codeforces.786B.Legacy(线段树优化建图 最短路Dijkstra)
题目链接 \(Description\) 有\(n\)个点.你有\(Q\)种项目可以选择(边都是有向边,每次给定\(t,u,v/lr,w\)): t==1,建一条\(u\to v\)的边,花费\(w\ ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- Luogu P3783 [SDOI2017]天才黑客
题目大意 一道码量直逼猪国杀的图论+数据结构题.我猪国杀也就一百来行 首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息.让你找一个点出发到其它点的最短路径.听起来很简单 ...
- 洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)
成功又一次自闭了 怕不是猪国杀之后最自闭的一次 一看到最短路径. 我们就能推测这应该是个最短路题 现在考虑怎么建图 根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并 ...
随机推荐
- Mon失效处理方法
假设环境为三个mon,主机名为mon1.mon2.mon3,现在mon3上面的系统盘损坏,mon的数据完全丢失,现在需要对mon3进行恢复处理 1.停止所有mon进程,可以不停其他进程,需要停止内核客 ...
- [LeetCode题解]19. 删除链表的倒数第N个节点 | 双指针 + 一次遍历
解题思路 双指针:第一个指针先走 n 步,然后两个指针同时走. 这里要注意当链表长度<=n,要删除头节点. 代码 /** * Definition for singly-linked list. ...
- [LeetCode题解]23. 合并K个升序链表 | 分治 + 递归
方法一:分治 + 递归 解题思路 在21. 合并两个有序链表,我们知道如何合并两个有序链表.而本题是合并 k 个有序链表,可以通过大问题拆分成小问题解决,即把 k 个链表,拆分成 k/2 个链表组,俩 ...
- linux shell简单快捷方式与通配符(元字符)echo -e文本显示颜色
1.shell常用快捷方式 ^R 搜索历史命令^D 退出^A 光标移动到命令行最前^E 光标移动到命令行最后^L 清屏^U 光标之前删除^K 光标之后删除^Y 撤销^S 锁屏^Q 解锁 2.多条命令执 ...
- Javaweb项目页面实时显示后台处理结果
http://www.cnblogs.com/dong-xu/p/6701271.html 此博文甚好,项目参照博主代码可实现. 前端页面: <%@ page language="ja ...
- 如何个性化定制iview中的table样式
使用renderHeader.render函数 例子: column:[ {}, ..., { title:'较年初占比变化', key:''lastYearChange, renderHeader: ...
- 企业级工作流解决方案(十)--集成Abp和ng-alain--权限系统
权限系统 应用系统离不开权限控制,权限中心不一定能抽象出所有的业务场景,这里定义的权限系统不一定能够满足所有的场景,但应该可以满足多数的业务需求. Abp的zero项目也定义了权限相关的表,但里面很多 ...
- 2个快速制作完成一幅思维导图的iMindMap思维导图用法
随着思维导图的流行,与其相关的思维导图制作软件如雨后春笋,纷纷进入我们的视野中,更让人难以选择.那想要入门的萌新该如何开始这个新的旅途呢? 各式各样的思维导图制作软件当中,有一个软件得到了大家一致的好 ...
- 利用MathType在Word里输入几何符号的技巧
通过学习几何学的知识,我们发现其中包含的几何符号有很多,比如有表示图形的符号,如三角形,平行四边形,圆,角,圆弧等:还有表示位置关系的符号,如平行,垂直等:还有表示矢量等其他符号,那这些符号怎么打出来 ...
- nginx学习http_auth_basic_module模块
对2.html页面做授权操作 先进行账号密码的生成 使用 htpasswd -c /etc/nginx/auth_conf 用户名 输入2次密码 (如果没有htpasswd,可以使用yum - ...