洛谷 5112 FZOUTSY 题解

题意简述

给定字符串 $S$,设 $suf(i)$ 表示从 $i$ 开始的后缀。支持 $q$ 个询问,每次给定 $[l,r]$,求这段区间中有多少对 $i,j$ 使得 $suf(i)$ 和 $suf(j)$ 的最长公共前缀长度 $\ge k$。$k$ 是一个定值,每次都一样。

(备注:$(i,j)$ 和 $(j,i)$ 是同样的一对,只算一次)

$k\le |S|\le 3\times 10^6,m\le 10^5$,并且满足 $n^2m\le 10^{15}$。

思路

$n^2m\le 10^{15}$?这看起来很奇怪。

冷静分析一下,这提示着我们,$O(n\sqrt{m})$ 的算法是可以通过的。仔细想一下 ,带根号的静态区间询问的算法…

莫队

然后,$suf(i),suf(j)$ 最长公共前缀长度 $\ge k$,等价于:$i$ 往后 $k$ 个的子串,和 $j$ 往后 $k$ 个的子串相同。

然后我们要快速比较两端子串是否相同…

哈希

于是问题变为:

  1. 处理第 $i$ 位往后 $k$ 个位置的哈希值,设为 $h[i]$
  2. 每次询问,就相当于询问 $h[l,r]$ 中有多少不重复的数,显然可以莫队维护。

代码

点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
namespace Flandre_Scarlet
{
#define N 3000006
#define ll long long
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define iter(a,p) (a.begin()+p)
int I()
{
int x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return (x=(f==1)?x:-x);
}
void Rd(int cnt,...)
{
va_list args; va_start(args,cnt);
F(i,1,cnt) {int* x=va_arg(args,int*);(*x)=I();}
va_end(args);
}

int n,m,k; char a[N];
void Input()
{
Rd(3,&n,&m,&k);
scanf("%s",a+1);
}

ll pre[N],pw[N];
ll RHash(int l,int r) {return pre[r]-pre[l-1]*pw[r-l+1];} // Range Hash,求区间哈希值

ll h[N]; unordered_map<ll,int> desc;

struct node{int l,r,id;}q[N]; int sn; bool operator<(node a,node b){return (a.l/sn<b.l/sn) or (a.l/sn==b.l/sn and a.r<b.r);}
// 莫队
// sn 为块大小
ll cnt[N]; ll cur=0;
void Add(int x){cur+=cnt[h[x]]; cnt[h[x]]++;} // 加入一个位置
void Del(int x){cnt[h[x]]--; cur-=cnt[h[x]];} // 删除一个位置
ll ans[N];

void Soviet()
{
pw[0]=1; F(i,1,n) pw[i]=pw[i-1]*233ll; // 哈希底数开大点
F(i,1,n) pre[i]=(pre[i-1]*233ll+(a[i]-'a'));

F(i,1,n-k+1) h[i]=RHash(i,i+k-1); // 预处理出 h[i] 数组
int dcnt=0;
F(i,1,n-k+1)
{
if (!desc[h[i]]) desc[h[i]]=++dcnt;
h[i]=desc[h[i]];
}
// 离散化
// 只是一个重新编号
// 我们只保证:原来相等的还相等,原来不相等的还不相等
// 并不保证 < 的关系 (也没有必要)

sn=n/sqrt(m);
// 块长开 n/sqrt(m)
// 据说开 sqrt(n) 也不会被卡
F(i,1,m) q[i]=(node){I(),min(I(),n-k+1),i};
// 因为右端点 >n-k+1 的时候显然不可能有长度为 k 的最长公共前缀
// 所以 r 和 n-k+1 取 min
sort(q+1,q+m+1);
int l=1,r=0; cur=0ll;
F(i,1,m)
{
if (q[i].l>q[i].r) {ans[q[i].id]=0; continue;} // 会有这种情况...判掉
while(r<q[i].r) ++r,Add(r);
while(r>q[i].r) Del(r),--r;
while(l<q[i].l) Del(l),++l;
while(l>q[i].l) --l,Add(l);
ans[q[i].id]=cur;
}
F(i,1,m) printf("%lld\n",ans[i]);
}

#define Flan void
Flan IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}
w