HDU70847 Pty loves string

传送门


一句话题意:给你一个字符串\(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;
}
posted @ 2021-09-01 16:42  mrclr  阅读(42)  评论(0编辑  收藏  举报