[Hackerrank University Codesprint 5] Interesting Trip (拓扑dp+可持久化线段树维护哈希+二分加速比较字典序)
[Hackerrank University Codesprint 5] Interesting Trip
拓扑dp+可持久化线段树维护哈希+二分加速比较字典序
因为是有向无环图,所以考虑拓扑排序上 dp。当前 \(u\) 的 dp 值肯定要么是 \(s\rightarrow u\) 的最小字典序,要么是 \(u\rightarrow f\) 的最小字典序,考虑后者。
那么设 \(f_u\) 表示 \(u\) 节点到终点的最小字典序。对于边 \((u,v)\),\(f_u=s_u+\min(f_v)\)。\(\min(f_v)\) 表示所有 \(v\rightarrow f\) 的最小字典序。
考虑字典序的比较,朴素需要 \(O(n)\) 枚举到第一个不同字符。发现关键在于找到最长公共前缀,这可以用前缀哈希二分在 \(O(\log n)\) 的时间内找到。
现在需要时刻维护 \(f_u\) 的前缀哈希,可以用可持久化线段树维护哈希,每次由 \(f_v\) 的版本的前端插入 \(s_u\) 构成 \(f_u\)。线段树中维护区间哈希值,区间长度,实现单点修改,区间查询。
关于拓扑排序的实现,普通的做法是队列,但是这样转移时发送式的,而我们需要比较所有的 \(v\),所以并不方便。于是可以用 dfs 反向遍历(即从 \(s\) 开始),在回溯过程中维护,也能用拓扑序遍历整个图。
复杂度 \(O(n\log^2 n)\),一遍过了,有点惊讶。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10, p = 20242024, mod = 1234567891;
int n, m, idx, s, tt;
int a[N], f[N];
std::vector<int> e[N];
i64 pw[N];
struct SEG {
int l, r, len;
i64 v;
} t[N * 20];
void pushup(int u) {
t[u].v = (t[t[u].l].v * pw[t[t[u].r].len] % mod + t[t[u].r].v) % mod;
t[u].len = t[t[u].l].len + t[t[u].r].len;
}
void ins(int &o, int u, int l, int r, int x, int y) {
o = ++idx;
t[o] = t[u];
if(l == r) {
t[o] = {l, r, 1, y};
return;
}
int mid = (l + r) >> 1;
if(x <= mid) ins(t[o].l, t[u].l, l, mid, x, y);
else ins(t[o].r, t[u].r, mid + 1, r, x, y);
pushup(o);
}
SEG qry(int u, int l, int r, int L, int R) {
if(L <= l && r <= R) return t[u];
int mid = (l + r) >> 1;
if(R <= mid) return qry(t[u].l, l, mid, L, R);
if(L > mid) return qry(t[u].r, mid + 1, r, L, R);
SEG ret = {0, 0, 0, 0}, ls = qry(t[u].l, l, mid, L, R), rs = qry(t[u].r, mid + 1, r, L, R);
ret.v = (ls.v * pw[rs.len] % mod + rs.v) % mod, ret.len = ls.len + rs.len;
return ret;
}
bool compare(int u, int v) {
if(!f[u]) return 1; //直接覆盖
else if(!f[v]) return 0; // v 无用
int l = 1, r = std::min(t[f[u]].len, t[f[v]].len), ret = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(qry(f[u], 1, n, n - t[f[u]].len + 1, n - t[f[u]].len + mid).v != qry(f[v], 1, n, n - t[f[v]].len + 1, n - t[f[v]].len + mid).v) r = mid - 1;
else l = mid + 1, ret = mid;
}
if(ret == std::min(t[f[u]].len, t[f[v]].len)) {
if(t[f[u]].len == ret) return 0;
else return 1;
} //特判 s in t | t in s 的情况
return qry(f[u], 1, n, n - t[f[u]].len + ret + 1, n - t[f[u]].len + ret + 1).v > qry(f[v], 1, n, n - t[f[v]].len + ret + 1, n - t[f[v]].len + ret + 1).v;
}
void print(int u) {
if(t[u].l == t[u].r) {
std::cout << (char)(t[u].v + 'a');
return;
}
if(t[u].l) print(t[u].l);
if(t[u].r) print(t[u].r);
}
int vis[N];
void dfs(int u) {
vis[u] = 1;
int mn = 0;
for(auto v : e[u]) {
if(!vis[v]) dfs(v);
if(compare(mn, v)) mn = v;
}
if(f[mn]) ins(f[u], f[mn], 1, n, n - t[f[mn]].len, a[u]);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
std::string S;
std::cin >> S;
S = "#" + S;
for(int i = 1; i <= n; i++) a[i] = S[i] - 'a';
pw[0] = 1;
for(int i = 1; i <= n; i++) pw[i] = pw[i - 1] * p % mod;
for(int i = 1; i <= m; i++) {
int u, v;
std::cin >> u >> v;
e[u].pb(v);
}
std::cin >> s >> tt;
ins(f[tt], 0, 1, n, n, a[tt]);
dfs(s);
if(!f[s]) std::cout << "No way\n";
else {
print(f[s]);
}
return 0;
}