传送门
一句话题意:给你一个字符串(S),(q)组询问,每次给定两个数(x,y),求由长度为(x)的前缀和长度为(y)的后缀拼接而成的字符串在(S)中的出现次数。
这题比赛的时候我差一点就做出来了,卡在了二维数点上。
不过正解比我简单一些,因为只有一个模式串,所以不用SAM或AC自动机了,用kmp就行。
那么,对于一个匹配位置([i, i + x + y - 1]),就有(S_{1 dots x}=S_{i dots i + x - 1},S_{j dots j + y - 1} = S_{|S| - y + 1 dots |S|}),且有(i + x = j).
如果做过P5829 【模板】失配树这道题,很容易想到第一个条件就是fail树上(x)的子树。而如果把原串反过来,那么第二个条件就是反串的fail树上(y)的子树。
于是问题就变成了,给你两棵树,每次询问两个子树中编号相同的点的个数,即二维数点问题。
对于二维数点问题,我们可以采用在线的主席树做法或者离线后扫描线+树状数组的做法。这里用主席树求解。
我们先把第一棵树的dfs序求出来,那么(x)的子树就是一段连续的dfs序。把第一棵树每个节点的dfs序映射到第二棵树上,就相当于在第二棵树(y)的子树中求有多少个节点的dfs序在([dfn[x], dfn[x] + size[x] - 1])中,而这个,用基于dfs序的主席树就可以做出来。总而言之,其主要思想就是让一维有序,另一维用数据结构维护和查找。
比赛的时候我用SAM想到了类似fail树的构建方法,但是因为没有再用反串构建一遍fail树,所以二维数点不是判断相等,而是差值等于(y),就不知道怎么维护了。
#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 5;
const int maxt = 4e6 + 5;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
int n, m;
char s[maxn];
vector<int> V1[maxn], V2[maxn];
int f[maxn];
In void kmp_init()
{
f[1] = 0, V1[0].push_back(1);
for(int i = 2, j = 0; i <= n; ++i)
{
while(j && s[j + 1] != s[i]) j = f[j];
if(s[j + 1] == s[i]) ++j;
f[i] = j;
V1[j].push_back(i);
}
reverse(s + 1, s + n + 1);
f[1] = 0, V2[0].push_back(1);
for(int i = 2, j = 0; i <= n; ++i)
{
while(j && s[j + 1] != s[i]) j = f[j];
if(s[j + 1] == s[i]) ++j;
f[i] = j;
V2[j].push_back(i); //反串坐标与原来相反
}
}
int siz1[maxn], dfn1[maxn], cnt1 = 0;
In void dfs1(int now)
{
siz1[now] = 1, dfn1[now] = ++cnt1;
for(auto v : V1[now]) dfs1(v), siz1[now] += siz1[v];
}
struct Tree
{
int ls, rs, sum;
}t[maxt];
int root[maxn], tcnt = 0;
In void insert(int old, int& now, int l, int r, int x)
{
t[now = ++tcnt] = t[old];
t[now].sum++;
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) insert(t[old].ls, t[now].ls, l, mid, x);
else insert(t[old].rs, t[now].rs, mid + 1, r, x);
}
In int query(int old, int now, int l, int r, int L, int R)
{
if(!old && !now) return 0;
if(l == L && r == R) return t[now].sum - t[old].sum;
int mid = (l + r) >> 1;
if(R <= mid) return query(t[old].ls, t[now].ls, l, mid, L, R);
else if(L > mid) return query(t[old].rs, t[now].rs, mid + 1, r, L, R);
else return query(t[old].ls, t[now].ls, l, mid, L, mid) + query(t[old].rs, t[now].rs, mid + 1, r, mid + 1, R);
}
int siz2[maxn], dfn2[maxn], cnt2 = 0;
In void dfs2(int now)
{
siz2[now] = 1, dfn2[now] = ++cnt2;
if(now) insert(root[cnt2 - 1], root[cnt2], 1, n + 1, dfn1[n - now]);
for(auto v : V2[now]) dfs2(v), siz2[now] += siz2[v];
}
In void init()
{
for(int i = 0; i <= n; ++i) V1[i].clear(), V2[i].clear();
cnt1 = cnt2 = tcnt = 0;
Mem(root, 0);
}
int main()
{
int T = read();
while(T--)
{
n = read(), m = read();
init();
scanf("%s", s + 1);
kmp_init();
dfs1(0), dfs2(0);
for(int i = 1; i <= m; ++i)
{
int x = read(), y = read();
write(query(root[dfn2[y] - 1], root[dfn2[y] + siz2[y] - 1], 1, n + 1, dfn1[x], dfn1[x] + siz1[x] - 1)), enter;
}
}
return 0;
}