[LOJ2720] 「NOI2018」你的名字(后缀自动机+线段树合并)

题意

  • 给你一个字符串 S \rm S S q \rm q q次询问一个区间 [ l , r ] \rm[l,r] [l,r],与一个字符串 T \rm T T,求 T \rm T T有多少个本质不同的子串没在 S [ l , r ] \rm S[l,r] S[l,r]中出现过。

首先求的东西可以转化成 T \rm T T中本质不同的子串减去 S [ l , r ] \rm S[l,r] S[l,r] T \rm T T的本质不同的公共子串数, T \rm T T中本质不同的子串用SAM很好求,那么现在问题来到了如何求 S [ l , r ] \rm S[l,r] S[l,r] T \rm T T的本质不同的公共子串数。

考虑 68 \rm 68 68分的暴力,计算每次询问 S \rm S S整个串时的答案,我们用 T \rm T T串在 S \rm S S串的SAM上运行,对于 T \rm T T的每个前缀找到其能在 S \rm S S上匹配到的最大后缀的长度,为了去重我们再对 T \rm T T串建出SAM,遍历每个状态计算对答案的贡献,就是所在状态满足任何一个Endpos的前缀所匹配到的最大后缀长度限制内的字串个数。

如果我们想知道对于任意 l , r \rm l,r l,r的答案怎么办呢,我们还是考虑计算 T \rm T T的每个前缀找到其能在 S \rm S S上匹配到的最大后缀的长度,为了保证遍历的状态在 [ l , r ] \rm [l,r] [l,r]内,我们用线段树合并维护出 S \rm S S每个状态的Right集合, T \rm T T S \rm S S上运行的时候只要判断转移后的状态是否存在一个Endpos在 [ l + l e n , r ] \rm[l+len,r] [l+len,r]之间就好了,复杂度 O ( ∑ ∣ T ∣ log ⁡ ∣ S ∣ ) \rm O(\sum|T|\log|S|) O(TlogS)



#include <bits/stdc++.h>

#define x first
#define y second
#define pb push_back
#define mp make_pair
#define inf (0x3f3f3f3f)
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(), x.end()
#define Set(a, b) memset(a, b, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define debug(x) cout << #x << " = " << x << endl
#define Rep(i, a) for (int i = 0, i##end = (a); i < i##end; ++ i)
#define For(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++ i)
#define Forr(i, a, b) for (int i = (a), i##end = (b); i >= i##end; -- i)
#define Travel(i, u) for (int i = head[u], v = to[i]; i; v = to[i = nxt[i]])

namespace IO { 

	const int N = 1e6;

	static char s[N], *S = s, *T = s, t[N], *E = t;

	inline void flush() { fwrite(t, 1, E - t, stdout), E = t; }

	inline char getc() {
		if (S == T) T = (S = s) + fread(s, 1, N, stdin);
		return S == T ? 0 : *S ++;
	}

	inline void putc(char c) {
		if (E == t + N - 1) flush();
		*E ++ = c;
	}
}

using IO::getc;
using IO::putc;
using IO::flush;
using namespace std;
using ll = long long;
using PII = pair <int, int>;

template <class T>
inline T read() {
	T ___ = 1, __ = getc(), _ = 0;
	for (; !isdigit(__); __ = getc())
		if (__ == '-') ___ = -1;
	for (; isdigit(__); __ = getc())
		_ = _ * 10 + __ - 48;
	return _ * ___;
}

template <class T>
inline void write(T _, char __ = '\n') {
	if (!_) putc(48);
	if (_ < 0) putc('-'), _ = -_;
	static int sta[111], tp;
	for (sta[tp = 0] = __; _; _ /= 10)
		sta[++ tp] = _ % 10 + 48;
	while (~tp) putc(sta[tp --]);
}

template <class T>
inline bool chkmax(T &_, T __) {
	return _ < __ ? _ = __, 1 : 0;
}

template <class T>
inline bool chkmin(T &_, T __) {
	return _ > __ ? _ = __, 1 : 0;
}

inline void proStatus() {
	ifstream t("/proc/self/status");
	cerr << string(istreambuf_iterator <char> (t), istreambuf_iterator <char> ());
}

const int N = 1e6;

int rt[N + 5], n;

struct Segment_Tree { 

#define ls(x) (T[x].ch[0])
#define rs(x) (T[x].ch[1])
#define mid ((l + r) >> 1)

	int cnt;

	struct node { int ch[2], s; } T[N << 5];

	void update(int &bh, int l, int r, int x) { 
		++ T[bh = bh ? bh : ++ cnt].s;
		if (l ^ r) {
			if (x <= mid) update(ls(bh), l, mid, x);
			else update(rs(bh), mid + 1, r, x);
		}
	}

	int merge(int x, int y) { 
		if (!x || !y) return x + y;
		int t = ++ cnt;
		T[t].s = T[x].s + T[y].s;
		ls(t) = merge(ls(x), ls(y));
		rs(t) = merge(rs(x), rs(y));
		return t; 
	}

	int query(int bh, int l, int r, int x, int y) { 
		if (x > y) return 0;
		if (x <= l && r <= y) return T[bh].s;
		if (y <= mid) return query(ls(bh), l, mid, x, y);
		if (x > mid) return query(rs(bh), mid + 1, r, x, y);
		return query(ls(bh), l, mid, x, y) + query(rs(bh), mid + 1, r, x, y);
	}

} Tr;

struct Suffix_Automaton { 

	int trans[N + 5][26], len[N + 5], link[N + 5], lst, cnt;

	void Clear() { 
		For(i, 1, cnt) { 
			len[i] = link[i] = ed[i] = size[i] = 0;
			Rep(j, 26) trans[i][j] = 0;
		} 
		cnt = lst = 1; 
	}

	int size[N + 5], ed[N + 5];

	int extend(int to, int id) { 
		int p = lst, now = lst = ++ cnt;
		len[now] = len[p] + (size[now] = 1), ed[now] = id;
		for (; p && !trans[p][to]; p = link[p]) 
			trans[p][to] = now;
		if (!p) link[now] = 1; 
		else { 
			int x = trans[p][to], y;
			if (len[x] == len[p] + 1) link[now] = x; 
			else { 
				len[y = ++ cnt] = len[p] + 1, ed[y] = ed[x]; 
				Cpy(trans[y], trans[x]), link[y] = link[x];
				for (; p && trans[p][to] == x; p = link[p])
					trans[p][to] = y;
				link[now] = link[x] = y;
			}
		}
		return now;
	}

	//* 
	int tong[N + 5], tp[N + 5];

	void Topsort() { 
		Rep(i, cnt) tong[i] = 0;
		For(i, 1, cnt) ++ tong[len[i]];
		For(i, 1, cnt) tong[i] += tong[i - 1];
		For(i, 1, cnt) tp[tong[len[i]] --] = i;
		Forr(i, cnt, 1) {
			int u = tp[i], dad = link[u];
			rt[dad] = Tr.merge(rt[dad], rt[u]);
		}
	}// */

	ll all() { 
		ll res = 0;
		For(i, 1, cnt) res += len[i] - len[link[i]];
		return res; 
	}

} S, T;

int mxlen[N + 5];

void run(Suffix_Automaton &SAM, char *s, int len, int l, int r)  { 
	int now = 1, res = 0;
	For(i, 1, len) { 
		int to = s[i] - 'a';
		if (SAM.trans[now][to] && Tr.query(rt[SAM.trans[now][to]], 1, n, l + res, r)) 
			++ res, now = SAM.trans[now][to];
		else { 
			while (now && (!SAM.trans[now][to] || !Tr.query(rt[SAM.trans[now][to]], 1, n, l + res, r))) {
				if (!res) { now = 0; break; }
				if (-- res == SAM.len[SAM.link[now]]) now = SAM.link[now];
			}
			if (!now) res = 0, now = 1; 
			else ++ res, now = SAM.trans[now][to];
		}
		mxlen[i] = res;
	}
}

ll query(Suffix_Automaton &SAM) { 
	ll res = 0;
	For(i, 2, SAM.cnt) 
		res += max(0, min(mxlen[SAM.ed[i]], SAM.len[i]) - SAM.len[SAM.link[i]]);
	return res;
}

char s[N + 5], t[N + 5];

signed main() {

	freopen("name.in", "r", stdin);
	freopen("name.out", "w", stdout);

	int m, q, x, y; 

	scanf("%s", s + 1);
	n = strlen(s + 1), S.Clear();
	For(i, 1, n) Tr.update(rt[S.extend(s[i] - 'a', i)], 1, n, i);
	S.Topsort();

	for (scanf("%d", &q); q -- ; ) { 
		scanf("%s%d%d", t + 1, &x, &y);
		m = strlen(t + 1), T.Clear();
		For(i, 1, m) T.extend(t[i] - 'a', i);
		run(S, t, m, x, y), write(T.all() - query(T)); 
#ifdef ylsakioi
		break;
#endif
	}

	return flush(), 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值