zoukankan      html  css  js  c++  java
  • 【洛谷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();
    }
    
  • 相关阅读:
    Windows Server 设置自动登陆
    Kettle学习笔记(四)— 总结
    Kettle学习笔记(一)— 环境部署及运行
    Web项目自动打开并且全屏
    数据库SQL Server 2016“功能选择”详细说明及精简安装选择
    kettle学习笔记(三)— 定时任务的脚本执行
    Kettle学习笔记(二)— 基本操作
    effective C++ 条款 54:让自己熟悉包括TR1在内的标准程序库
    [转]基于MFC的ActiveX控件开发
    effective C++ 条款 50:了解new和delete的合理替换时机
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/12058537.html
Copyright © 2011-2022 走看看