[Hackerrank University Codesprint 5] Interesting Trip (拓扑dp+可持久化线段树维护哈希+二分加速比较字典序)

[Hackerrank University Codesprint 5] Interesting Trip

拓扑dp+可持久化线段树维护哈希+二分加速比较字典序

因为是有向无环图,所以考虑拓扑排序上 dp。当前 u 的 dp 值肯定要么是 su 的最小字典序,要么是 uf 的最小字典序,考虑后者。

那么设 fu 表示 u 节点到终点的最小字典序。对于边 (u,v)fu=su+min(fv)min(fv) 表示所有 vf 的最小字典序。

考虑字典序的比较,朴素需要 O(n) 枚举到第一个不同字符。发现关键在于找到最长公共前缀,这可以用前缀哈希二分O(logn) 的时间内找到。

现在需要时刻维护 fu 的前缀哈希,可以用可持久化线段树维护哈希,每次由 fv 的版本的前端插入 su 构成 fu。线段树中维护区间哈希值,区间长度,实现单点修改,区间查询。

关于拓扑排序的实现,普通的做法是队列,但是这样转移时发送式的,而我们需要比较所有的 v,所以并不方便。于是可以用 dfs 反向遍历(即从 s 开始),在回溯过程中维护,也能用拓扑序遍历整个图。

复杂度 O(nlog2n),一遍过了,有点惊讶。

#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 @   Fire_Raku  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示