zoukankan      html  css  js  c++  java
  • [HDU-6791] 2020HDU多校第三场T1(回文自动机)

    [HDU-6791] 2020HDU多校第三场T1(回文自动机)

    前置知识:

    1.字符串的( ext{Border})

    2.回文自动机

    3.回文串与( ext{Border})

    3.1:回文串的( ext{Border})也是回文串

    若有回文串(S)的一个( ext{Border} :T),则(S_{1,|T|}=S_{|S|-|T|+1,|S|}=reverse(S_{1,|T|}))

    (T)也是一个回文串

    3.2:遍历回文自动机的(fail)链,能得到当前串的所有( ext{Border})(基于3.1得到)

    约定:串(S)( ext{Border})集合为(B(S)),字符集为(Sigma)

    题意:

    设随机空串末尾添加(Sigma)中的字符,第一次出现子串(S)的期望长度为(E(S))

    给定一个串,每次查询它的两个回文子串(A,B),比较(E(A),E(B))

    起源?

    一切的起源都是" 国家集训队论文2018 :1-浅谈生成函数在掷骰子问题上的应用 "的一个结论。。。

    还有为什么会是回文子串呢?因为只有回文自动机能访问子串的所有( ext{Border})。。。

    结论 以及 口胡证明?

    (egin{aligned}E(S)=sum_{Tin B(S)}|Sigma|^{|T|}end{aligned})(???)

    在原论文给出了生成函数性的证明,实际可以直接口胡(好吧也差不多),大致分成两个步骤

    1.(E(S)=sum_{i=0}^{infty})长度为(i)依然不包含(S)的概率(即把长度为(i)时恰好合法转化为了(0..i-1)时不合法)

    2.设所有长度下不合法的串集合为(G)(每个不合法串有概率(G(T))),合法的串集合为(F)(每个合法串也有概率(F(T)))

    由第一步(E(S)=sum G(T)),合法串的概率不会重复,所以(sum F(T)=1)

    考虑(G)中所有的串,如果在后面接上(S)必然合法,但是可能在更早的时候就结束了,这是必然满足接上的前缀是( ext{Border})

    也就是说,在(G)集合后面接上(S)后,不仅会得到(F)集合,还会得到(F)集合后面额外接上(|S|-|R|,(Rin B(S)))长度字符的状态

    所以有(sum G(T)cdot (frac{1}{|Sigma|})^{|S|}=sum_{Rin B(S)}sum F(T)cdot (frac{1}{|Sigma|})^{|S|-|R|})

    化简且带入(sum F(T)=1),得到(E(S)=sum G(T)=egin{aligned}sum_{Rin B(S)}|Sigma|^{|R|}end{aligned})

    那么比较问题就落到了比较( ext{Border})上面

    视答案为为一个(26)进制数从高位到低位比较,转化为直接从大到小比较( ext{Border})序列的字典序即可

    建出回文自动机后,倍增找到当前查询串对应的状态,所有的( ext{Border})就是(fail)链上所有非空状态长度

    比较字典序可以

    1.倍增+hash

    2.可以根据( ext{Border})的性质分解为等差数列后暴力比较

    3.像后缀数组一样,倍增地去为所有节点的字典序排序,这样查询是(O(1))

    hash应该细节比较少,但是常数大

    以下是hash版本

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
    
    char IO;
    template <class T=int> T rd(){
    	T s=0;int f=0;
    	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return f?-s:s;
    }
    
    const int N=1e5+10;
    const ll P1=1e9+13,P2=19260817;
    const ll K1=123213,K2=342525;
    
    int n;
    char s[N];
    int now,len[N],fail[N],nxt[N][26],pos[N],cnt;
    int Pow1[N],Pow2[N];
    int fa[N][18],h1[N][18],h2[N][18];
    
    void Init(){
    	rep(i,0,cnt) memset(nxt[i],fail[i]=0,104);
    	len[1]=-1;
    	fail[now=0]=fail[1]=cnt=1;
    }
    int Find(int x,int y){
    	while(s[y]!=s[y-len[x]-1]) x=fail[x];
    	return x;
    }
    void Extend(int i,int c){
    	now=Find(now,i);
    	if(!nxt[now][c]){
    		fail[++cnt]=nxt[Find(fail[now],i)][c];
    		len[nxt[now][c]=cnt]=len[now]+2;
    	}
    	pos[i]=now=nxt[now][c];
    }
    int Que(int l,int p){
    	l=p-l+1,p=pos[p];
    	drep(i,17,0) if(len[fa[p][i]]>=l) p=fa[p][i];
    	return p;
    }
    
    int main(){
    	rep(i,Pow1[0]=Pow2[0]=1,N-1) Pow1[i]=1ll*Pow1[i-1]*K1%P1,Pow2[i]=Pow2[i-1]*K2%P2;
    	rep(kase,1,rd()){
    		Init(),n=rd(),scanf("%s",s+1);
    		rep(i,1,n) Extend(i,s[i]-'a');
    		rep(i,2,cnt) {
    			fa[i][0]=fail[i],h1[i][0]=h2[i][0]=len[i];
    			rep(j,1,17){
    				fa[i][j]=fa[fa[i][j-1]][j-1];
    				if(fa[i][j]>1){
    					h1[i][j]=(1ll*h1[i][j-1]*Pow1[1<<(j-1)]+h1[fa[i][j-1]][j-1])%P1;
    					h2[i][j]=(1ll*h2[i][j-1]*Pow2[1<<(j-1)]+h2[fa[i][j-1]][j-1])%P2;
    				}
    			}
    		}
    		rep(q,1,rd()) {
    			int A=rd(),B=rd(),C=rd(),D=rd();
    			A=Que(A,B),C=Que(C,D);
    			drep(i,17,0) if(fa[A][i]>1 && fa[C][i]>1 && h1[A][i]==h1[C][i] && h2[A][i]==h2[C][i]) A=fa[A][i],C=fa[C][i];
    			A=max(len[A],0),C=max(len[C],0);
    			if(A==C) puts("draw");
    			else if(A<C) puts("sjfnb");
    			else puts("cslnb");
    		}
    	}
    }
    
    
    
  • 相关阅读:
    Python学习9——异常
    提取微信小游戏代码
    linux 命令记录
    cpp 线程传递参数
    c++ primer 记录1
    你不知道的js
    js中的对象 函数 原型
    C++ 获取时间
    linux 常见命令
    git 的基本命令
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13397598.html
Copyright © 2011-2022 走看看