Codeforces Bubble Cup 11 J. Moonwalk challenge加强版

题意

有一棵 \(n\) 个节点的树,每条边上有一个字符,有 \(m\) 次询问。

每次会选定两个点 \(u, v\)\(u\)\(v\) 的路径上的字符形成了一个字符串 \(T\) ,再选定一个字符串 \(S\) ,计算 \(S\)\(T\) 中的出现次数。

\(n, m \le 10^5, \sum |S| \le 5 \times 10^6\)

题解

原题多了一个保证 \(|S| \le 100\) 那么信息不会很多,直接倍增即可保存所有信息,复杂度是 \(𝑂(m|S| + n|S| \log(n|S|) + m \log n(\log n + |S|))\)

显然对于 \(S\) 的长度限制是没有必要的。

询问一个串在另外一个串中出现的次数,查询串是给你的,但每次询问的时候不好把母串放进来跑,那么通常可以利用 \(SA/SAM\) 解决。

然后我们考虑是询问路径,我们进行树链剖分。那么我们只需要对于每个剖分的链,只需要询问这个串在其中一段出现多少次即可。

这可以用 \(SA\) + 主席树或者 \(SAM\) + 二维数点实现,都是单次询问 \(O(\log n)\) 的。

然后我们需要考虑跨轻边的部分如何计算,这一部分可以每次花 \(O(|S|)\) 的代价做 \(\mathrm{KMP}\) 匹配。

那么 \(SAM\) 做就是 \(O(n|\Sigma| + (m \log n + \sum |S|) \log n)\)\(SA\)\(O(n \log n + (m \log n+ \sum |S|)\log n)\)

其实还可以做到更优秀,我们对于整颗树建一颗广义 \(SAM\) 。(但是此处实现会有很多问题)

其中一种也许可行的实现方式是在 \(DFS\) 每次插入一个字母的时候,从父亲处继承 \(Last\) ,然后直接插入,这是本人的实现方式。

但他们说这样不仅复杂度可能会被卡到 \(O(n \sqrt n)\) 并且还有可能有正确性问题。

正确的建法是 \(BFS\) 逐层添加字符,这样就是复杂度和正确性都是正确的。

这样建完后,我们每次相当于查询整个 \(fail\) 树上上的一个子树在另外一颗数上 \(dfn\) 的一段连续区间。

那么这个用主席树合并实现就可以避免离线的细节了。

然后还有一个细节,有可能有些计算进去的串向上延伸会超过 \(lca\) ,那么一开始查询的时候限制一下它向上延伸的高度即可,此处树剖没有那么好实现,利用倍增即可。

这样的话,我们只需要在 \(lca\) 处计算跨链的贡献即可。

复杂度就优化成了 \(O(n|\Sigma| + n\log n + m \log^2 n + \sum |S|)\)

由于信息可减,查询链上信息。其实可以利用括号序列把复杂度优化成 \(O(n|\Sigma| + n\log n + m \log n + \sum |S|)\)

代码

\(SAM\) 处可能会存在问题,仅供参考。

最后就是 广义SAM + 主席树合并 + 树链剖分 + 倍增 + KMP 即可。

写了 \(6.7KB\) 。。。想死了。。。

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back

using namespace std;

typedef unsigned long long ull;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
	int x(0), sgn(1); char ch(getchar());
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
	for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
	return x * sgn;
}

inline char read_char() {
	char ch(getchar());
	for (; !islower(ch); ch = getchar());
	return ch;
}

void File() {
	freopen ("e.in", "r", stdin);
	freopen ("e.out", "w", stdout);
}

const int N = 1e5 + 1e3, M = N << 1;

#define mid ((l + r) >> 1)
#define lson ls[o], l, mid
#define rson rs[o], mid + 1, r

template<int Maxn>
struct President_Tree {

	int ls[Maxn], rs[Maxn], sumv[Maxn], Size;

	void Update(int &o, int l, int r, int up) {
		if (!o) o = ++ Size;
		if (l == r) { ++ sumv[o]; return ; }
		up <= mid ? Update(lson, up) : Update(rson, up);
		sumv[o] = sumv[ls[o]] + sumv[rs[o]];
	}

	int Query(int o, int l, int r, int ql, int qr) {
		if (!o) return 0;
		if (ql <= l && r <= qr) return sumv[o];
		if (qr <= mid) return Query(lson, ql, qr);
		if (ql > mid) return Query(rson, ql, qr);
		return Query(lson, ql, qr) + Query(rson, ql, qr);
	}

	int Merge(int x, int y) {
		if (!x || !y) return x | y;
		int o = ++ Size;
		ls[o] = Merge(ls[x], ls[y]);
		rs[o] = Merge(rs[x], rs[y]);
		sumv[o] = sumv[x] + sumv[y]; return o;
	}

};

President_Tree<N * 120> T;

int n;

template<int Maxn, int Alpha>
struct Suffix_Automaton {

	int trans[Maxn][Alpha], link[Maxn], len[Maxn], num[Maxn], Size;

	Suffix_Automaton() { Size = 1; }

	inline int Extend(int Last, int id, int pos) {
		int cur = ++ Size, p = Last; len[cur] = len[Last] + 1; num[cur] = pos;
		for (; p && !trans[p][id]; p = link[p]) trans[p][id] = cur;
		if (!p) link[cur] = 1;
		else {
			int q = trans[p][id];
			if (len[q] == len[p] + 1) link[cur] = q;
			else {
				int clone = ++ Size;
				Cpy(trans[clone], trans[q]);
				len[clone] = len[p] + 1; link[clone] = link[q];
				for (; p && trans[p][id] == q; p  = link[p]) trans[p][id] = clone;
				link[cur] = link[q] = clone;
			}
		}
		return cur;
	}

	vector<int> G[Maxn]; int rt[Maxn];

	void Dfs(int u) {
		for (int v : G[u])
			Dfs(v), rt[u] = T.Merge(rt[u], rt[v]);
	}

	void Build() {
		For (i, 2, Size) {
			G[link[i]].pb(i); 
			if (num[i]) T.Update(rt[i], 1, n, num[i]);
		}
		Dfs(1);
	}

	int Trans(char *str) {
		int u = 1;
		For (i, 1, strlen(str + 1)) {
			int id = str[i] - 'a';
			if (!trans[u][id]) return 0;
			u = trans[u][id];
		}
		return u;
	}

};

Suffix_Automaton<N * 2, 26> SAM;

int Head[N], Next[M], to[M], val[M], e = 0;

inline void add_edge(int u, int v, int w) {
	to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w;
}

int anc[N][20];

int sz[N], son[N], pid[N], fa[N], dep[N], ch[N];

void Dfs_Init(int u, int from = 0) {
	sz[u] = 1; dep[u] = dep[anc[u][0] = fa[u] = from] + 1;
	for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) 
		if (v != from) {
			ch[v] = val[i];
			Dfs_Init(v, u); sz[u] += sz[v];
			if (sz[v] > sz[son[u]]) son[u] = v;
		}
}

int dfn[N], top[N];

void Dfs_Part(int u) {
	static int clk = 0; dfn[u] = ++ clk;
	if (u > 1)
		pid[u] = SAM.Extend(pid[fa[u]], ch[u], clk);
	top[u] = son[fa[u]] == u ? top[fa[u]] : u;
	if (son[u]) Dfs_Part(son[u]);
	for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) 
		if (v != fa[u] && v != son[u]) Dfs_Part(v);
}

int Lca(int x, int y) {
	for (; top[x] != top[y]; x = fa[top[x]])
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
	return dep[x] < dep[y] ? x : y;
}

int Calc(int x, int y, int pos) {
	int res = 0;
	for (; top[x] != top[y]; x = fa[top[x]]) {
		int l = dfn[top[x]], r = dfn[x];
		res += T.Query(SAM.rt[pos], 1, n, l, r);
	}
	return res + T.Query(SAM.rt[pos], 1, n, dfn[y], dfn[x]);
}

const int Len = 5e6 + 1e3;

char str[Len]; int Log2[N];

struct KMP {

	void Get_Fail(char *S, int *fail) {
		For (i, 2, strlen(S + 1)) {
			int j = fail[i - 1];
			while (j && S[j + 1] != S[i]) j = fail[j];
			fail[i] = S[j + 1] == S[i] ? j + 1 : 0;
		}
	}

	int Match(char *S, char *T, int *fail) {
		int lenT = strlen(T + 1), j = 0, res = 0;
		For (i, 1, strlen(S + 1)) {
			while (j && T[j + 1] != S[i]) j = fail[j];
			if (T[j + 1] == S[i]) ++ j;
			if (j == lenT) ++ res;
		}
		return res;
	}

};

KMP FUCK;

char S[Len], tmp[Len]; int fail[Len];

int MATCH(int x, int y) {

	int lca = Lca(x, y), len = 0, lt = 0;
	for (; x != lca; x = fa[x]) S[++ len] = ch[x] + 'a';
	for (; y != lca; y = fa[y]) tmp[++ lt] = ch[y] + 'a';
	reverse(tmp + 1, tmp + lt + 1);

	For (i, 1, lt) S[++ len] = tmp[i];
	FUCK.Get_Fail(str, fail); S[len + 1] = '\0';

	return FUCK.Match(S, str, fail);

}

int Ancestor(int x, int k) {
	Fordown (i, Log2[dep[x]], 0) 
		if (dep[anc[x][i]] >= k) x = anc[x][i];
	return x;
}

int Split(int x, int y, int lca) {
	int len = strlen(str + 1), gap = dep[lca] + (len - 1);
	if (len == 1) return 0;
	x = Ancestor(x, min(gap, dep[x]));
	y = Ancestor(y, min(gap, dep[y]));
	return MATCH(x, y);
}

int main () {

	File();

	n = read();
	For (i, 1, n - 1) {
		int u = read(), v = read(), ch = read_char() - 'a';
		add_edge(u, v, ch); add_edge(v, u, ch);
	}

	pid[1] = 1;

	Dfs_Init(1); Dfs_Part(1); SAM.Build();

	For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
	For (j, 1, Log2[n]) For (i, 1, n) 
		anc[i][j] = anc[anc[i][j - 1]][j - 1];

	int m = read();
	For (i, 1, m) {
		int u = read(), v = read(); scanf ("%s", str + 1);
		int len = strlen(str + 1);

		int lca = Lca(u, v), ans = 0, dis = dep[u] + dep[v] - 2 * dep[lca] + 1;
		if (len >= dis) { puts("0"); continue; }

		int gap = dep[lca] + len; 
		if (lca == u || lca == v) {
			if (v == lca) reverse(str + 1, str + len + 1);

			if (u == lca) swap(u, v);
			if (gap <= dep[u])
				ans = Calc(u, Ancestor(u, gap), SAM.Trans(str));
		} else {
			if (gap <= dep[v])
				ans = Calc(v, Ancestor(v, gap), SAM.Trans(str));
			reverse(str + 1, str + len + 1);
			if (gap <= dep[u])
				ans += Calc(u, Ancestor(u, gap), SAM.Trans(str));
			reverse(str + 1, str + len + 1);
			ans += Split(u, v, lca);
		}
		printf ("%d\n", ans);

	}

#ifdef zjp_shadow
	freopen ("/proc/self/status", "r", stdin);
	char str[1010];
	while (cin.getline(str, 1000))
		cerr << str << endl;
#endif

	return 0;

}
posted @ 2019-01-02 16:37  zjp_shadow  阅读(470)  评论(2编辑  收藏  举报