【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)

这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊

题目

洛谷 4482

分析

这题明明可以在线做的,为什么我见到的所有题解都是离线啊 …… 什么时候有机会出一个在线版本坑人。

题目的要求可以转化为求出一个最大的 (i(i<r)) 满足 (i-mathrm{lcs}(i,r)<l) ,其中 (mathrm{lcs}(i,r)) 表示 前缀 (i)前缀 (r) 的最长公共后缀。答案就是 (i-l+1)

在 SAM 上考虑这个问题。fa 树、max、(R_u) 等的定义见 【知识总结】后缀自动机的构建 。两个前缀的 lcs 就是它们对应的点在 fa 树上的 lca 的最长长度。下面来随意证明一下:

设两个 前缀 对应的结点为 (u)(v) 。根据 fa 树的定义,(u) 到根的路径上的所有点都是 (R_u) 的子集并且元素数量递减(即 (u) 的长度递减的后缀),对于 (v) 也是如此。那么 (R_ucap R_v) 就是最大的既是 (R_u) 的子集又是 (R_v) 的子集的集合(即 lcs 对应的集合),也就是 (R_{mathrm{lca}(u,v)}) 。既然是两个前缀,那么它们一定是等价字符串中最长的。因此如果 LCA 就是 (u) 或者 (v) 是合法的。否则,(mathrm{lca}(u,v)) 的最长长度一定小于 (u)(v) 的长度,因此也是合法的。

以下 (i)(r) 均指对应前缀所在的点,树均指 fa 树。(m_u) 表示结点 (u) 的最长长度(即 (u) 的 max)。

根据以上结论,我们有了一个暴力的做法:枚举 (r) 的祖先 (p) 作为 (lca(i,r)) ,这样最长公共后缀的长度就是固定的 (m_p) 了。在 (p) 的子树中找到最大的 (i) 满足 (i<r)(i-m_p<l) 即可。虽然会重复计算,但如果 (p) 不是 (mathrm{lca}(i,r)) 而是 (mathrm{lca}(i,r)) 的祖先,那么 (i-m_p) 大于 (i-m_{mathrm{lca}(i,r)}) ,相当于限制反而更严格了。如果此时 (i) 满足条件,那么 (i) 一定是合法的。在每个点上建立一棵线段树来维护它的所有儿子,位置 (i) 的权值是 (i-m_p) ,然后线段树上二分出最大的 (i<r) 且权值小于 (l) 即可。(当然,现在也可以不引入「权值」,然后直接查找最大的小于 (min(r, l+m_p)) 的点。以后再说为什么一定要引入「权值」)。

这种做法有两个瓶颈:一个是要枚举所有祖先,每次询问复杂度是 (O(n)) ;另一个是需要在线段树中共计插入 (O(n^2)) 个点。以下分别来解决。

为了优化枚举祖先的时间,考虑树链剖分(好像这里通常叫「链分治」),每次直接爬到重链顶端。设进入重链的第一个点为 (p) ,重链顶端为 (t) 。怎么一次性在 (p)(t) 路径上的所有点的线段树上查询呢?由于我们已经很机智地把和路径上具体的点 (u) 有关的信息 (i-m_u) 作为了权值而非下标,所以可以直接把这些点的线段树全部合并到 (p) 的线段树上,然后直接查 (p) 的线段树就相当于查完了从 (p)(t) 的所有线段树。具体地,沿着重链从上往下合并,类似于前缀和。因为可能从任意一个有轻儿子的点进入重链,所以合并的时候要复制一份,不能修改原树。这样复杂度仍然是对的。具体合并方法和复杂度证明见 【知识总结】线段树合并及其复杂度证明

下面来解决第二个瓶颈。剖都剖了,应该能想到一个性质:所有点的轻子树的大小之和是 (O(nlog n)) ,因为一个点到根的路径上只有 (O(log n)) 条轻边。于是,我们不把整棵子树都插进线段树,而是只插轻子树和这个点本身。考虑如果从 (p) 进入一条重链,哪些本该考虑到的点(即和 (r) 的 lca 在这条重链上的点)没有考虑到呢?通过画图可以轻易发现,这些点一定都在 (p) 的子树中。那么直接在 (p) 的子树中求最大的 (i) 满足 (i<min(r,l+m_p)) 。这可以通过线段树合并和线段树上二分(这里的线段树和上面说的线段树没有关系)实现。

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
using namespace std;

namespace zyt
{
	const int N = 2e5 + 10, CH = 26, B = 20, INF = 0x3f3f3f3f;
	int n, pos[N], id[N << 1];
	vector<int> g[N << 1];
	char str[N];
	namespace Tree_Chain_Cut
	{
		int size[N << 1], fa[N << 1], son[N << 1], top[N << 1];
		void dfs1(const int u, const int f)
		{
			size[u] = 1;
			fa[u] = f;
			for (vector<int>::iterator it = g[u].begin(); it != g[u].end(); it++)
			{
				int v = *it;
				dfs1(v, u);
				size[u] += size[v];
				if (size[v] > size[son[u]])
					son[u] = v;
			}
		}
		void dfs2(const int u, const int t)
		{
			top[u] = t;
			if (son[u])
				dfs2(son[u], t);
			for (vector<int>::iterator it = g[u].begin(); it != g[u].end(); it++)
			{
				int v = *it;
				if (v == son[u])
					continue;
				dfs2(v, v);
			}
		}
	}
	class Segment_Tree
	{
	private:
		struct node
		{
			int min, lt, rt;
		}*tree;
		int cnt;
	public:
		int *rot;
		Segment_Tree(const int n = 0)
		{
			cnt = 0;
			rot = new int[n + 1];
			memset(rot, 0, sizeof(int[n + 1]));
			tree = new node[n * B * 2];
		}
		void change(int &rot, const int lt, const int rt, const int pos, const int x)
		{
			if (!rot)
				rot = ++cnt, tree[rot].min = INF, tree[rot].lt = tree[rot].rt = 0;
			tree[rot].min = min(tree[rot].min, x);
			if (lt == rt)
				return;
			int mid = (lt + rt) >> 1;
			if (pos <= mid)
				change(tree[rot].lt, lt, mid, pos, x);
			else
				change(tree[rot].rt, mid + 1, rt, pos, x);
		}
		int merge(const int x, const int y)
		{
			if (!x || !y)
				return x + y;
			int a = ++cnt;
			tree[a].min = min(tree[x].min, tree[y].min);
			tree[a].lt = merge(tree[x].lt, tree[y].lt);
			tree[a].rt = merge(tree[x].rt, tree[y].rt);
			return a;
		}
		int query(const int rot, const int lt, const int rt, const int r, const int lim)
		{
			if (!rot)
				return -1;
			if (lt == rt)
				return tree[rot].min < lim ? lt : -1;
			int mid = (lt + rt) >> 1;
			if (mid + 1 < r && tree[rot].rt && tree[tree[rot].rt].min < lim)
			{
				int tmp = query(tree[rot].rt, mid + 1, rt, r, lim);
				if (~tmp)
					return tmp;
			}
			return query(tree[rot].lt, lt, mid, r, lim);
		}
	}t1, t2;
	namespace Suffix_Auto_Chicken
	{
		int ctoi(const char c)
		{
			return c - 'a';
		}
		struct node
		{
			int fa, max, s[CH];
		}tree[N << 1];
		int cnt, last;
		void init()
		{
			cnt = last = 1;
		}
		void insert(const char c, const int id)
		{
			int p = last, np = ++cnt, x = ctoi(c);
			tree[np].max = tree[p].max + 1;
			while (p && !tree[p].s[x])
				tree[p].s[x] = np, p = tree[p].fa;
			if (!p)
				tree[np].fa = 1;
			else
			{
				int q = tree[p].s[x];
				if (tree[q].max == tree[p].max + 1)
					tree[np].fa = q;
				else
				{
					int nq = ++cnt;
					memcpy(tree[nq].s, tree[q].s, sizeof(int[CH]));
					tree[nq].max = tree[p].max + 1;
					tree[nq].fa = tree[q].fa;
					tree[q].fa = tree[np].fa = nq;
					while (p && tree[p].s[x] == q)
						tree[p].s[x] = nq, p = tree[p].fa;
				}
			}
			pos[id] = last = np;
		}
		void build()
		{
			static int count[N], buf[N << 1];
			init();
			for (int i = 0; i < n; i++)
				insert(str[i], i), t1.change(t1.rot[pos[i]], 0, n - 1, i, i);
			memset(id, -1, sizeof(int[cnt + 1]));
			for (int i = 0; i < n; i++)
				id[pos[i]] = i;
			memset(count, 0, sizeof(int[n + 1]));
			for (int i = 1; i <= cnt; i++)
				++count[tree[i].max];
			for (int i = 1; i <= n; i++)
				count[i] += count[i - 1];
			for (int i = 1; i <= cnt; i++)
				buf[count[tree[i].max]--] = i;
			for (int i = cnt; i > 1; i--)
			{
				t1.rot[tree[buf[i]].fa] = t1.merge(t1.rot[tree[buf[i]].fa], t1.rot[buf[i]]);
				g[tree[buf[i]].fa].push_back(buf[i]);
				//fprintf(stderr, "%d %d
", tree[buf[i]].fa, buf[i]);
			}
		}
	}
	void dfs(const int u, const int r, const int d)
	{
		if (~id[u])
			t2.change(t2.rot[r], 0, n - 1, id[u], id[u] - d);
		for (vector<int>::iterator it = g[u].begin(); it != g[u].end(); it++)
			dfs(*it, r, d);
	}
	int work()
	{
		using namespace Tree_Chain_Cut;
		using namespace Suffix_Auto_Chicken;
		scanf("%s", str);
		n = strlen(str);
		t1 = Segment_Tree(n * 3), t2 = Segment_Tree(n * 8);
		build(), dfs1(1, 0), dfs2(1, 1);
		for (int i = 1; i <= cnt; i++)
		{
			if (~id[i])
				t2.change(t2.rot[i], 0, n - 1, id[i], id[i] - tree[i].max);
			for (vector<int>::iterator it = g[i].begin(); it != g[i].end(); it++)
				if (*it != son[i])
					dfs(*it, i, tree[i].max);
		}
		for (int i = 1; i <= cnt; i++)
			if (i == top[i])
			{
				int tmp = son[i];
				while (tmp)
					t2.rot[tmp] = t2.merge(t2.rot[tmp], t2.rot[fa[tmp]]), tmp = son[tmp];
			}
		int q;
		scanf("%d", &q);
		while (q--)
		{
			int l, r;
			scanf("%d%d", &l, &r);
			--l, --r;
			int p = pos[r], ans = 0;
			while (p)
			{
				ans = max(ans, t1.query(t1.rot[p], 0, n - 1, r, l + tree[p].max));
				ans = max(ans, t2.query(t2.rot[p], 0, n - 1, r, l));
				p = fa[top[p]];
			}
			printf("%d
", max(0, ans - l + 1));
		}
		return 0;
	}
}
int main()
{
	return zyt::work();
}
原文地址:https://www.cnblogs.com/zyt1253679098/p/12058537.html