[BZOJ2754][SCOI2012]喵星球上的点名(后缀数组+莫队)

本文介绍使用后缀数组解决洛谷P2336、BZOJ2754和LOJ#2374问题的方法。通过在姓名间插入无关字符,将问题转化为模式串匹配问题,再利用后缀数组和树状数组求解。文章详细阐述了如何判断模式串是否为主串子串,以及如何统计模式串和主串之间的匹配数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Address

洛谷P2336
BZOJ2754
LOJ#2374

Solution

考虑在每个人的姓和名之间插入一个无关的字符。
这样问题就转化成了一些主串和一些模式串,询问每个模式串能匹配到多少个主串,以及每个主串能匹配到多少个模式串。
把所有的主串和所有的模式串用无关字符连接起来构成一个串 S S S ,并对 S S S 串求后缀数组。
(注:上面用到的所有无关字符必须两两不同,否则会出锅)
先考虑一个弱化:判断一个模式串是否是一个主串的子串。
找到 S S S 中这个主串对应的下标集合 S A S_A SA ,以及模式串的开头在 S S S 中的对应下标位置 x x x
那么模式串是主串的子串,当且仅当存在 y ∈ S A y\in S_A ySA
使得 L C P ( S [ x . . . ∣ S ∣ ] , S [ y . . . ∣ S ∣ ] ) ≥ L LCP(S[x...|S|],S[y...|S|])\ge L LCP(S[x...S],S[y...S])L 。其中 L L L 为模式串的长度。
众所周知, h e i g h t height height 数组可以将 L C P LCP LCP 转成区间最小值。
所以我们可以利用树状数组等,求出 [ l , r ] [l,r] [l,r] 表示满足 h e i g h t height height 的区间 [ l + 1 , x ] [l+1,x] [l+1,x] 和 区间 [ x + 1 , r ] [x+1,r] [x+1,r] 的最小值都不小于 L L L ,并且 l l l 最小, r r r 最大。
那么再次转化:判断是否存在 i ∈ [ l , r ] i\in[l,r] i[l,r] 满足 s a [ i ] ∈ S A sa[i]\in S_A sa[i]SA
S S S 的每个下标一个 c o l [ i ] col[i] col[i] :如果 S [ i ] S[i] S[i] 来自第 k k k 个主串则 c o l [ i ] = k col[i]=k col[i]=k ,否则为 0 0 0
于是,判断一个模式串是否能匹配主串 k k k ,就转化成是否存在 i ∈ [ l , r ] i\in[l,r] i[l,r] 满足 c o l [ s a [ i ] ] = k col[sa[i]]=k col[sa[i]]=k
这样,求一个模式串能匹配多少个主串,就相当于统计一个区间内出现了多少种不同的数。
这是莫队的经典应用。
对于第二问,我们可以在莫队移动指针的过程中,维护每个数 x x x 最近一次进入区间的时间 t x t_x tx ,然后如果在 r r r 时刻 x x x 移出了区间,则为 x x x 的答案贡献 r − t x r-t_x rtx
最后区间内还有一些数,需要为其中的每个数 x x x 的答案贡献 m − t x + 1 m-t_x+1 mtx+1

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Pow(k, n) for (k = 1; k < n; k <<= 1, std::swap(x, y))
#define Bitr(x, n) for (; x <= n; x += x & -x)
#define Bitl(x) for (; x; x -= x & -x)

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
T Max(T a, T b) {return a > b ? a : b;}

const int N = 5e5 + 5;

int n, m, T, s[N], w[N], sa[N], rank[N], height[N], id[N],
l1, l2, pos[N], len[N], ans[N], lst[N], sna[N], A[N], S,
cnt[N], now_ans;

void change(int x, int v)
{
	Bitr(x, T) A[x] = Max(A[x], v);
}

int ask(int x)
{
	int res = 0;
	Bitl(x) res = Max(res, A[x]);
	return res;
}

struct query
{
	int l, r, bl, id;
} que[N];

bool comp(query a, query b)
{
	return a.bl < b.bl || (a.bl == b.bl && a.r < b.r);
}

void build_sa(int n, int q)
{
	int i, k, *x = rank, *y = height, m = 0;
	For (i, 1, n) w[m = Max(m, s[i]), x[i] = s[i]]++;
	For (i, 2, m) w[i] += w[i - 1];
	For (i, 1, n) sa[w[x[i]]--] = i;
	Pow(k, n)
	{
		int tt = 0;
		For (i, n - k + 1, n) y[++tt] = i;
		For (i, 1, n) if (sa[i] > k) y[++tt] = sa[i] - k;
		For (i, 1, m) w[i] = 0;
		For (i, 1, n) w[x[i]]++;
		For (i, 2, m) w[i] += w[i - 1];
		Rof (i, n, 1) sa[w[x[y[i]]]--] = y[i];
		m = 0;
		For (i, 1, n)
		{
			int u = sa[i], v = sa[i - 1];
			y[u] = x[u] != x[v] || x[u + k] != x[v + k] ? ++m : m;
		}
		if (m == n) break;
	}
	if (y != rank) std::copy(y, y + n + 1, rank);
	k = 0;
	For (i, 1, n)
	{
		if (k) k--;
		while (s[i + k] == s[sa[rank[i] - 1] + k]) k++;
		height[rank[i]] = k;
	}
	For (i, 2, n)
	{
		change(height[i] + 1, i);
		if (pos[sa[i]])
		{
			int x = ask(len[pos[sa[i]]]);
			que[pos[sa[i]]].l = x ? x : 1;
		}
	}
	memset(A, 0, sizeof(A));
	Rof (i, n, 2)
	{
		if (pos[sa[i]])
		{
			int x = ask(len[pos[sa[i]]]);
			que[pos[sa[i]]].r = x ? n - x : n;
		}
		change(height[i] + 1, n - i + 1);
	}
	S = sqrt(n);
	For (i, 1, q) que[i].id = i, que[i].bl = (que[i].l - 1) / S + 1;
	std::sort(que + 1, que + q + 1, comp);
}

int main()
{
	int i, j;
	n = read(); m = read();
	For (i, 1, n)
	{
		l1 = read();
		For (j, 1, l1) s[++T] = read() + 1, id[T] = i;
		s[++T] = 10001 + (i << 1) - 1;
		l2 = read();
		For (j, 1, l2) s[++T] = read() + 1, id[T] = i;
		s[++T] = 10001 + (i << 1);
	}
	For (i, 1, m)
	{
		len[i] = read();
		pos[T + 1] = i;
		For (j, 1, len[i]) s[++T] = read() + 1;
		s[++T] = 10001 + (n << 1) + i;
	}
	build_sa(T, m);
	int l = 1, r = 0;
	For (i, 1, m)
	{
		int tl = que[i].l, tr = que[i].r;
		while (r < tr) if (!(cnt[id[sa[++r]]]++))
			now_ans++, lst[id[sa[r]]] = i;
		while (l > tl) if (!(cnt[id[sa[--l]]]++))
			now_ans++, lst[id[sa[l]]] = i;
		while (r > tr) if (!(--cnt[id[sa[r--]]]))
			now_ans--, sna[id[sa[r + 1]]] += i - lst[id[sa[r + 1]]];
		while (l < tl) if (!(--cnt[id[sa[l++]]]))
			now_ans--, sna[id[sa[l - 1]]] += i - lst[id[sa[l - 1]]];
		ans[que[i].id] = now_ans - (cnt[0] != 0);
	}
	For (i, 1, m) printf("%d\n", ans[i]);
	For (i, 1, n) if (cnt[i]) sna[i] += m - lst[i] + 1;
	For (i, 1, n) printf("%d ", sna[i]);
	std::cout << std::endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值