【题解】无畏造八强(双哈+马拉车+根号分治)
【题解】无畏造八强(双哈+马拉车+根号分治)
题面
无畏造八强(wwzbq)
题目背景
我亮牌子我抗塔,我和凯南贴脸打。四路经济2800,世界第一吹jb。
题目描述
你有n个字符集为小写字符的字符串\(T_i\).
现在有q次询问,每次询问一个\(x_i,y_i\)
对于每次询问,你应该求出:\(T_{x_i},T_{y_i}\)中的公共的本质不同的回文子串个数。
题目会利用一些手段强制在线。下面是一些可能有用的定义:
回文串:一个串翻转过来仍与其自己相同。
本质相同:对于两个子串,不考虑其在原串中的位置是否相同,只要长度相同并且对应位置字符相同。
本质不同:对于两个子串并不本质相同。输入格式
第一行是两个整数n
接下来n行,第i行为一个非空字符串\(T_i\)
接下来是一行一个整数
接下来q行,第i行有两个非负整数\(x_i', y_i'\)真正的询问为
\(x_i = x_i' \bigoplus (ans_{i-1})\), \(y_i = y_i' \bigoplus (ans_{i-1})\)
\(ans_i\)表示第i次询问得到的答案,其中\(ans_0 = 0\).保证\(1 \leq x_i, y_i \leq n\).
\(\bigoplus\)代表按位异或操作,对应c++中的^输出格式
对于每次询问输出一个整数代表答案。
输入样例1
2
aba
aaa
1
1 2输出样例1
1
子任务
对于\(20\%\)的数据,保证\(\sum |T_i|,q\leq300\)
对于\(40\%\)的数据,保证\(\sum |T_i|,q\leq5000\)
对于另外\(20\%\)的数据,保证\(\min\{|T_{x_i}|,|T_{y_i}|\}<=100\)
对于另外\(20\%\)的数据,保证\(n=3\)
对于\(100\%\)的数据,\(\sum |T_i|<=10^5\)
\(sol\)
正如标题所说的那样,双哈+马拉车+根号分治...
这题的大致思路是:对于字符串哈希,马拉车预处理所有本质不同的回文子串,哈希塞到set里面,然后根据\(\sum |T_i|<=10^5\)进行根号分治...
其实不用回文树有点假?因为这道题字符集是\(26\),加上马拉车要用的\(1\)都到了\(27\),然而双哈希最多存\(x^2=19260817\times19491001\)种的不同的字符串,然而字符串可能的集合都到了至少\(26^{10000}\)...或许这就是哈希吧...
结论1:一个串本质不同的回文子串个数是\(O(n)\)的
证明:假设在原有的串最后面加上一个字符c,这个字符会造成的贡献一定是以c为结尾的回文串,然而c最多产生一个新的回文串,这个回文串就是是以c的最长那个的回文串。为什么一定是最长的那个回文串?因为这个最长的回文串S一定包含了那些比S短的小回文串,这些回文串小\(\{a\}\)由于被包括在S中,所以一定根据回文串的对称性,小\(\{a\}\)一定在在S串中,出现了至少两次。
结论2:本质不同的回文子串一定是以某个点结尾的最长的那个回文串
证明:根据结论一和数学归纳法可以得出。
到了这一步,我们就把问题变为了:对于一个集群\(\{S\},\sum |S_i|\le10^5\),有\(q\le 10^5\)组询问,问$|S_i\cap S_j | $的大小。
对于一组询问,我们可以枚举较小的那个集合,然后在较大的那个集合里查询是否存在,然后把答案备用...你以为这只是常数优化,其实你复杂度就突然对了。
结论3:本质不同的询问最多造成本质不同\(O(n\sqrt n)\)种枚举(忽略那个\(\log\)和\(q\))
考虑\(|S|\ge \sqrt n\)的集合个数$\le \sqrt n $个...这很应该显然吧?
-
假设查询中有一个小于\(\sqrt n\)的集合,枚举它,那么复杂度就是\(O(\sqrt n)\)了。
-
假设询问的两个都是大于等于\(\sqrt n\)的怎么办?那么考虑大于\(\sqrt n\)的集合,它,这些集合大小加起来是\(O(n)\)的,但是由于\(size > \sqrt n\)的集合只有\(\sqrt n\)个,那么对于它来说,最多只有\(\sqrt n\)次查询,那么就是\(O(n \sqrt n)\)了。
本质上是确定一个按数据范围分做法的临界,使得两种做法的最坏复杂度一样,往往此时也是渐进意义下的最优解。
你直接拿\(map\)存一下询问,然后记忆化一下就好了。
其实代码调了很久,这是带注释版本
没带注释版本,记得开C++11
//@winlere https://paste.ubuntu.com/p/x6fqZWBDdp/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
using namespace std; typedef long long ll;
inline int qr(){register int ret=0,f=0;register char c=getchar();
while(c<48||c>57)f|=c==45,c=getchar();
while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;}
const int maxn=1e5+5;
const int seed1=233;
const int seed2=439;
const int mod1=19260817;
const int mod2=19491007;
const char c1='z'+1;
const char c2='z'+2;
int n,q,ans;
int p[maxn<<1];
int lef[maxn<<1];
int ha1[maxn<<1];
int ha2[maxn<<1];
int sav1[maxn<<1];
int sav2[maxn<<1];
map < pair < int , int > , int > que;
multiset < pair < int , int > > s[maxn];
inline int ksm(int base,const int&p,const int&mod){
register int ret=1;
for(register int t=p;t;t>>=1,base=1ll*base*base%mod)
if(t&1) ret=1ll*ret*base%mod;
return ret;
}
inline pair < int , int > get_ha(const int&l,const int&r){
return make_pair((ha1[r]-1ll*ha1[l-1]*sav1[r-l+1]%mod1+mod1)%mod1,(ha2[r]-1ll*ha2[l-1]*sav2[r-l+1]%mod2+mod2)%mod2);
}
inline int Min(const int&a,const int&b){
if(a<b) return a;
return b;
}
inline void find_pam(const int&num){
auto&S=s[num];
string now;
now.push_back(c1);
register char c=getchar();
while(c<'a'||c>'z') c=getchar();
while(c>='a'&&c<='z') now.push_back(c2),now.push_back(c),c=getchar();
for(register int t=1,edd=now.size()-1;t<=edd;++t){
ha1[t]=(1ll*ha1[t-1]*seed1%mod1+now[t]-'a'+1ll)%mod1;
ha2[t]=(1ll*ha2[t-1]*seed2%mod2+now[t]-'a'+1ll)%mod2;
}
for(register int t=1,edd=now.size();t<edd;++t)
p[t]=0,lef[t]=0x3f3f3f3f;
register int mid=0,r=0;
for(register int t=1,edd=now.size();t<edd;++t){
if(t<=r) p[t]=Min(r-t+1,p[(mid<<1)-t]);
lef[t+p[t]-1]=Min(lef[t+p[t]-1],t-p[t]+1);
while(now[t-p[t]]==now[t+p[t]]) lef[t+p[t]]=Min(lef[t+p[t]],t-p[t]),++p[t];
if(t+p[t]-1>r) mid=t,r=t+p[t]-1;
}
for(register int t=1,edd=now.size()-1;t<=edd;++t){
if(lef[t]>t||now[t]=='|') continue;
register auto t1=get_ha(lef[t],t);
if(S.find(t1)==S.end()) S.insert(t1);
}
}
int main(){
n=qr();
for(register int t=0,edd=maxn<<1;t<edd;++t) sav1[t]=ksm(seed1,t,mod1),sav2[t]=ksm(seed2,t,mod2);
for(register int t=1;t<=n;++t) find_pam(t);
q=qr();
for(register int t=1,t1,t2,t3=0;t<=q;++t,t3=0){
t1=ans^qr();t2=ans^qr();
if(t1>t2) swap(t1,t2);
register auto temp=make_pair(t1,t2);
if(que.find(temp)!=que.end())
printf("%d\n",ans=que[temp]);
else {
if(s[t1].size()>s[t2].size()) swap(t1,t2);
for(register auto f:s[t1])
if(s[t2].find(f)!=s[t2].end()) ++t3;
que.insert(make_pair(temp,ans=t3));
printf("%d\n",ans);
}
}
return 0;
}