Loading

[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;
}
posted @ 2024-07-02 12:22  Fire_Raku  阅读(59)  评论(0编辑  收藏  举报