【题解】无畏造八强(双哈+马拉车+根号分治)

【题解】无畏造八强(双哈+马拉车+根号分治)

1814 Clarke and string

题面

无畏造八强(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;
}

posted @ 2019-05-28 22:06  谁是鸽王  阅读(425)  评论(0编辑  收藏  举报