【洛谷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_u\cap 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(n\log 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\n", 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\n", max(0, ans - l + 1));
		}
		return 0;
	}
}
int main()
{
	return zyt::work();
}
posted @ 2019-12-18 10:31  Inspector_Javert  阅读(237)  评论(0编辑  收藏  举报