【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)

题目:

洛谷4770

UOJ395

分析:

一个很好的SAM应用题……

一句话题意:给定一个字符串\(S\)。每次询问给定字符串\(T\)和两个整数\(l\)\(r\),求\(T\)有多少个本质不同的非空子串不是\(S[l,r]\)的子串。

首先显然是“正难则反”,求有多少个本质不同的非空子串是\(S[l,r]\)的子串(下面的“答案”一词指的是这个值)。先考虑没有\(l\)\(r\)限制的情况。分别处理询问。对于\(S\)建出后缀自动机。枚举\(T\)的所有前缀,在\(S\)的后缀自动机上走(匹配)。每个前缀对答案的贡献就是这个前缀有多少后缀出现在\(S\)中,即当前在后缀自动机上匹配的深度。

但是直接把所有贡献都加起来是错的,因为没有保证本质不同。于是对\(T\)也建出后缀自动机,不统计每个前缀的贡献,而是统计每个结点的贡献,即统计每个结点对应的本质不同的字符串中有多少个在\(S\)中出现。记\(lim[i]\)\(T\)的前缀\(i\)有多少个后缀在\(S\)中出现,那么一个结点\(p\)的贡献就是\(max(0,min(lim[i],max[p])-max[fa[p]])\),其中\(i\)\(p\)\(Right\)集合中的任意一个点。由于以\(Right\)集合中任意一点结尾的,长度不超过\(max[p]\)的子串都是相同的,所以\(i\)的选取不会影响\(min(lim[i],max[p])\)的值。

至于带\(l\)\(r\)限制的情况,用线段树合并求出\(S\)的后缀自动机上每个结点的\(Right\)集合。\(T\)\(S\)的后缀自动机上匹配时,要查询要走到的结点的\(Right\)集合与\([l+len,r]\)的交集是否非空(\(len\)是当前匹配长度,即查询是否有一个长为\(len+1\)的子串在\(S[l,r]\)中出现过)。

代码:

统计答案的时候直接算了这个结点对应的字符串中有多少个不是\(S[l,r]\)的子串,因此式子和上面分析的略有不同。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#define _ 0
using namespace std;

namespace zyt
{
	const int N = 5e5 + 10, B = 20;
	template<typename T>
	inline bool read(T &x)
	{
		char c;
		bool f = false;
		x = 0;
		do
			c = getchar();
		while (c != EOF && c != '-' && !isdigit(c));
		if (c == EOF)
			return false;
		if (c == '-')
			f = true, c = getchar();
		do
			x = x * 10 + c - '0', c = getchar();
		while (isdigit(c));
		if (f)
			x = -x;
		return true;
	}
	inline void read(string &s)
	{
		char buf[N];
		scanf("%s", buf);
		s = buf;
	}
	template<typename T>
	inline void write(T x)
	{
		static char buf[20];
		char *pos = buf;
		if (x < 0)
			putchar('-'), x = -x;
		do
			*pos++ = x % 10 + '0';
		while (x /= 10);
		while (pos > buf)
			putchar(*--pos);
	}
	typedef long long ll;
	const int CH = 26;
	inline int ctoi(const char c)
	{
		return c - 'a';
	}
	namespace Segment_Tree
	{
		struct node
		{
			int sum, s[2];
		}tree[(N << 1) * B];
		int cnt, head[N << 1];
		int insert(const int lt, const int rt, const int pos)
		{
			int rot = ++cnt;
			++tree[rot].sum;
			if (lt == rt)
				return rot;
			int mid = (lt + rt) >> 1;
			if (pos <= mid)
				tree[rot].s[0] = insert(lt, mid, pos);
			else
				tree[rot].s[1] = insert(mid + 1, rt, pos);
			return rot;
		}
		int merge(const int rot1, const int rot2)
		{
			if (!rot1)
				return rot2;
			if (!rot2)
				return rot1;
			int rot = ++cnt;
			int lt = merge(tree[rot1].s[0], tree[rot2].s[0]);
			int rt = merge(tree[rot1].s[1], tree[rot2].s[1]);
			tree[rot] = (node){tree[rot1].sum + tree[rot2].sum, lt, rt};
			return rot;
		}
		int query(const int rot, const int lt, const int rt, const int ls, const int rs)
		{
			if (ls > rs)
				return 0;
			if (ls <= lt && rt <= rs)
				return tree[rot].sum;
			int mid = (lt + rt) >> 1, ans = 0;
			if (ls <= mid)
				ans += query(tree[rot].s[0], lt, mid, ls, rs);
			if (rs > mid)
				ans += query(tree[rot].s[1], mid + 1, rt, ls, rs);
			return ans;
		}
	}
	struct SAM
	{
		int last, cnt;
		struct node
		{
			int max, fa, first, s[CH];
		}tree[N << 1];
		void init()
		{
			last = cnt = 1;
			memset(tree[1].s, 0, sizeof(int[CH]));
		}
		void insert(const char c)
		{
			int x = ctoi(c);
			int np = ++cnt, p = last;
			memset(tree[np].s, 0, sizeof(int[CH]));
			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[p].max + 1 == tree[q].max)
					tree[np].fa = q;
				else
				{
					int nq = ++cnt;
					memcpy(tree[nq].s, tree[q].s, sizeof(int[CH]));
					tree[nq].first = tree[q].first;
					tree[nq].max = tree[p].max + 1;
					tree[nq].fa = tree[q].fa;
					tree[np].fa = tree[q].fa = nq;
					while (p && tree[p].s[x] == q)
						tree[p].s[x] = nq, p = tree[p].fa;
				}
			}
			last = np;
		}
		static int buf[N << 1];
		void topo()
		{
			static int count[N];
			int maxx = 0;
			memset(count, 0, sizeof(count));
			for (int i = 1; i <= cnt; i++)
				++count[tree[i].max], maxx = max(maxx, tree[i].max);
			for (int i = 1; i <= maxx; i++)
				count[i] += count[i - 1];
			for (int i = cnt; i > 0; i--)
				buf[count[tree[i].max]--] = i;
		}
		void build(const string &s, const bool segment)
		{
			using Segment_Tree::head;
			init();
			for (int i = 0; i < s.size(); i++)
			{
				insert(s[i]);
				tree[last].first = i;
				if (segment)
					head[last] = Segment_Tree::insert(0, s.size() - 1, i);
			}
			if (segment)
			{
				topo();
				for (int i = cnt; i > 0; i--)
				{
					using Segment_Tree::head;
					head[tree[buf[i]].fa] = Segment_Tree::merge(head[tree[buf[i]].fa], head[buf[i]]);
				}
			}
		}
	}s1, s2;
	int SAM::buf[N << 1], lim[N];
	string s;
	ll solve(const string &t, const int l, const int r)
	{
		using Segment_Tree::head;
		using Segment_Tree::query;
		SAM::node *tree = s1.tree;
		int now = 1, len = 0;
		for (int i = 0; i < t.size(); i++)
		{
			int x = ctoi(t[i]), nxt = tree[now].s[x];
			while (now && !nxt)
				now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
			while (now)
			{
				int tmp = 0;
				while (len >= 0 && !(tmp = query(head[nxt], 0, s.size() - 1, l + len, r)))
				{
					--len;
					if (len == tree[tree[now].fa].max)
						now = tree[now].fa, nxt = tree[now].s[x];
				}
				if (tmp)
				{
					now = nxt, ++len;
					break;
				}
				else
					now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
			}
			if (!now)
				now = 1, len = 0;
			lim[i] = len;
		}
		ll ans = 0;
		for (int i = 1; i <= s2.cnt; i++)
			ans += max(0, s2.tree[i].max - max(s2.tree[s2.tree[i].fa].max, lim[s2.tree[i].first]));
		return ans;
	}
	int work()
	{
		int T;
		read(s), read(T);
		s1.build(s, true);
		while (T--)
		{
			using namespace Segment_Tree;
			static string t;
			int l, r;
			read(t), read(l), read(r);
			--l, --r;
			s2.build(t, false);
			write(solve(t, l, r)), putchar('\n');
		}
		return (0^_^0);
	}
}
int main()
{
	return zyt::work();
}
posted @ 2019-01-26 19:48  Inspector_Javert  阅读(252)  评论(0编辑  收藏  举报