「NOI2018」你的名字(SAM,线段树合并)

文章目录

【注意】最后更新于 January 31, 2021,文中内容可能已过时,请谨慎使用。

题目链接

UOJ

LOJ

洛谷

题意简述

给你一个字符串 $S$,有 $q$ 次询问,每次给你询问串 $T$ 以及左右端点 $l$, $r$,询问 $T$ 有多少个 本质不同 的子串 不是 $S[l..r]$ 的子串。

$|S|\le 5\cdot 10^5$, $\sum|T|\le 10^6$, $q\le 10^5$ 。

简要做法

不需要本质不同、l=1、r=|S|

枚举 $T$ 的前缀,找到这个前缀的最长后缀使其是 $S$ 的子串,就可以求出答案了。

换句话说,对于每个 $i$,求出最小的 $j$ ($1\le j\le i+1$) 使得 $T[j..i]$ 是 $S$ 的子串,那么 $\sum j-1$ 就是答案。

可以对 $S$ 建 SAM,维护一个指针指向 $T[j..i]$ 在 SAM 上对应的顶点,如果拓展一个字符后不是 $S$ 的子串就把 $j$ 加一,如果低于当前 SAM 上节点的长度限制就跳 parent 边,也就是 从首部删去字符

需要本质不同、l=1、r=|S|

由本质不同可以想到用 SAM 去重。具体来说,对 $T$ 建 SAM,对每个 $T[j..i]$ 在 SAM 上打个标记,表示 $T[j..i]$ 及其所有后缀(即对应顶点及其在 parent 树上的祖先)都是 $S$ 的子串,最后 DFS 一遍即可求出答案。

原问题

如果询问的不是整个 $S$ 而是其一个子串,关键在于如何判断 $T[j..i]$ 是不是 $S[l..r]$ 的子串。

可以使用线段树合并维护 $S$ 的 SAM 上的每个点的 right 集合,$T[j..i]$ 是 $S[l..r]$ 的子串等价于 $T[j..i]$ 在 $S$ 中的 right 集合含有某个区间内的元素,区间求和即可查询。

还有一个细节,在 $T$ 的 SAM 上打标记时,可能出现 SAM 上的一个节点对应的较短的那些串是 $S[l..r]$ 的子串,但较长的串不是。所以标记还需要记录不是 $S[l..r]$ 子串所需的长度。

参考代码

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

typedef long long ll;

struct SAM
{
	struct Node
	{
		int len, pa, tag;
		vector<int> ch;
		Node(int _len = 0, int _pa = -1, vector<int> _ch = vector<int>(26, 0)):
			len(_len), pa(_pa), tag(0), ch(_ch) {}
	};
	
	vector<Node> t;
	int p;
	vector<int> endp;
	vector<vector<int> > g;
	
	void extend(int x)
	{
		int np = t.size();
		t.emplace_back(t[p].len + 1);
		while (~p && !t[p].ch[x])
		{
			t[p].ch[x] = np;
			p = t[p].pa;
		}
		if (p == -1) t[np].pa = 0;
		else
		{
			int q = t[p].ch[x];
			if (t[q].len == t[p].len + 1) t[np].pa = q;
			else
			{
				int nq = t.size();
				t.emplace_back(t[p].len + 1, t[q].pa, t[q].ch);
				t[q].pa = t[np].pa = nq;
				while (~p && t[p].ch[x] == q)
				{
					t[p].ch[x] = nq;
					p = t[p].pa;
				}
			}
		}
		p = np;
	}
	
	SAM(const string& s): t(1), p(0)
	{
		t.reserve(s.size() * 2);
		endp.resize(s.size());
		for (int i = 0; i < s.size(); ++i)
		{
			extend(s[i] - 'a');
			endp[i] = p;
		}
		g.resize(t.size());
		for (int i = 1; i < t.size(); ++i) g[t[i].pa].push_back(i);
	}
	
	ll dfs(int u = 0)
	{
		ll out = 0;
		for (auto v : g[u])
		{
			if (!v) continue;
			out += dfs(v);
			t[u].tag = max(t[u].tag, t[v].tag);
		}
		if (u) out += max(0, t[u].len - max(t[u].tag, t[t[u].pa].len));
		return out;
	}
};

struct SegmentTree
{
#define mid ((l + r) >> 1)

	struct Node
	{
		int sum, ls, rs;
		Node(int _sum = 0, int _ls = 0, int _rs = 0): sum(_sum), ls(_ls), rs(_rs) {}
	};
	
	vector<Node> t;
	
	int add(int x, int p, int l, int r)
	{
		int cur = t.size();
		t.push_back(t[x]);
		++t[cur].sum;
		if (l == r - 1) return cur;
		if (p < mid) t[cur].ls = add(t[x].ls, p, l, mid);
		else t[cur].rs = add(t[x].rs, p, mid, r);
		return cur;
	}
	
	int merge(int x, int y)
	{
		if (!x || !y) return x | y;
		t.emplace_back(t[x].sum + t[y].sum, merge(t[x].ls, t[y].ls), merge(t[x].rs, t[y].rs));
		return t.size() - 1;
	}
	
	int query(int x, int L, int R, int l, int r)
	{
		if (L >= r || R <= l) return 0;
		if (L <= l && r <= R) return t[x].sum;
		return query(t[x].ls, L, R, l, mid) + query(t[x].rs, L, R, mid, r);
	}

#undef mid
} seg;

vector<int> rt;

void dfs(const vector<vector<int> >& g, int u = 0)
{
	for (auto v : g[u])
	{
		dfs(g, v);
		rt[u] = seg.merge(rt[u], rt[v]);
	}
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	string s;
	cin >> s;
	SAM sams(s);
	
	seg.t.resize(1);
	seg.t.reserve(s.size() << 5);
	
	rt.resize(sams.t.size(), 0);
	
	for (int i = 0; i < s.size(); ++i) rt[sams.endp[i]] = seg.add(rt[sams.endp[i]], i, 0, s.size());
	dfs(sams.g);
	
	int q;
	cin >> q;
	
	while (q--)
	{
		int l, r;
		string t;
		cin >> t >> l >> r;
		
		SAM samt(t);
		
		for (int i = 0, u = 0, v = 0, p = 0; i < t.size(); ++i)
		{
			while (p < i && !sams.t[u].ch[t[i] - 'a'])
			{
				++p;
				if (u && i - p <= sams.t[sams.t[u].pa].len) u = sams.t[u].pa;
				if (v && i - p <= samt.t[samt.t[v].pa].len) v = samt.t[v].pa;
			}
			if (!sams.t[u].ch[t[i] - 'a'])
			{
				p = i + 1;
				u = v = 0;
				continue;
			}
			u = sams.t[u].ch[t[i] - 'a'];
			v = samt.t[v].ch[t[i] - 'a'];
			while (p <= i && seg.query(rt[u], l + i - p - 1, r, 0, s.size()) == 0)
			{
				++p;
				if (u && i - p + 1 <= sams.t[sams.t[u].pa].len) u = sams.t[u].pa;
				if (v && i - p + 1 <= samt.t[samt.t[v].pa].len) v = samt.t[v].pa;
			}
			samt.t[v].tag = max(samt.t[v].tag, i - p + 1);
		}
		
		cout << samt.dfs() << '\n';
	}
	
	return 0;
}

评论正在加载中...如果评论较长时间无法加载,你可以 搜索对应的 issue 或者 新建一个 issue