zoukankan      html  css  js  c++  java
  • 【洛谷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('
    ');
    		}
    		return (0^_^0);
    	}
    }
    int main()
    {
    	return zyt::work();
    }
    
  • 相关阅读:
    Spring-data-jpa 笔记(一)
    grpc详解 java版
    快速入门正则表达式
    异常的处理
    一位资深程序员大牛给予Java初学者的学习路线建议
    this用法
    静态代码块与非静态代码块
    类的成员变量--构造器
    Java并发机制及锁的实现原理
    JAVA内存模型
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/10324739.html
Copyright © 2011-2022 走看看