• P6216 回文匹配


    KMP+manacher:https://www.luogu.com.cn/problem/P6216

    对于一对字符串 ((s_1,s_2)),若 (s_1) 的长度为奇数的子串 ((l,r)) 满足 ((l,r)) 是回文的,那么 (s_1) 的“分数”会增加 (s_2)((l,r)) 中出现的次数。
    现在给出一对 ((s_1,s_2),n=|s1|,m=|s2|),请计算出 (s_1) 的“分数”。
    答案对 (2 ^ {32}) 取模。


    首先因为要求 ((l,r)) 回文,所以可以用 manacher 求出每一个字符的回文半径,然后根据半径确定以它为对称轴的最长回文串 ((l,r))
    然后答案要加上 ((l,r),(l+1,r-1),(l+2,r-2),cdots)(s2) 出现次数和
    对于字串 ((l,r)),就看区间 ([l,r-m+1]) 中,有几个字符可以作为 (s2) 的开头并与之完全匹配
    这一点可以用 kmp 求出,以 (a_i) 表示 (s1_i) 能否作为 (s2) 的开头并和它完全匹配
    这里用前缀和,则求每个区间的复杂度是 (O(1)),但区间个数 (O(n)),还要继续优化
    表达成式子,方便表示,设 (L=l,R=r-m+1,maxk=lfloorfrac{R-L}{2} floor)

    [sum_{k=0}^{maxk}sum_{i=L+k}^{R-k}a_i ]

    [sum_{k=0}^{maxk}sum(R-k)-sum(L+k-1) ]

    [sum_{i=R-maxk}^{R}sum(i)-sum_{i=L-1}^{L+maxk-1}sum(i) ]

    发现这里的前缀和,也就是 (sum) 还是一个区间求和的形式,所以可以求一个二次前缀和(前缀和的前缀和),记之为 (summ)
    所以原来的式子就是:

    [summ(R)-summ(R-maxk-1)-summ(L+maxk-1)-summ(L-2) ]

    所以总复杂度就是 (O(n))

    题目里说 ((l,r)) 长度仅限奇数,所以 manacher 时不用像模板一样在字符间隙填充无意义字符
    但一开始没看到这点,等注意到时代码都写的差不多了,懒得改了

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    #define mod 4294967296ll
    int fail[3000005];
    char s1[3000005],s2[3000005];
    int len1,len2;
    int summ[3000005];
    char s[6000005];
    int n;
    int len[6000005];
    inline void KMP(){
    	fail[1]=0;
    	for(reg int j,i=2;i<=len2;i++){
    		j=fail[i-1];
    		while(j&&s2[j+1]!=s2[i]) j=fail[j];
    		if(j) fail[i]=j+1;
    		else fail[i]=(s2[1]==s2[i]);
    	}
    	for(reg int j=0,i=1;i<=len1;i++){
    		while(s1[i]!=s2[j+1]&&j) j=fail[j];
    		if(s1[i]==s2[j+1]) j++;
    		if(j==len2){
    			j=fail[j];
    			summ[i-len2+1]=1;
    		}
    	}
    }
    inline LL manacher(){
    	LL ans=0;
    	s[0]=1;s[1]=2;n=1;
    	for(reg int i=1;i<=len1;i++) s[++n]=s1[i],s[++n]=2;
    	s[n+1]=3;
    	reg int pos=0,max=0;
    	for(reg int L,R,i=1;i<=n;i++){
    		if(pos+max-1<i) len[i]=1;
    		else len[i]=std::min(len[pos+pos-i],pos+max-i);
    		while(s[i+len[i]]==s[i-len[i]]) len[i]++;
    		if(i+len[i]-1>=pos+max-1) pos=i,max=len[i];
    		L=(i-len[i]+2)>>1;R=((i+len[i]-2)>>1)-len2+1;
    //			printf("i : %d, id : %d, len[i] : %d, L : %d, R : %d
    ",i,i>>1,len[i],L,R);
    		if(s[i]!=2&&R>=L) ans+=summ[R]-summ[R-((R-L)>>1)-1]-summ[L+((R-L)>>1)-1]+summ[std::max(L-2,0)],ans%=mod;
    		//长度为奇数 
    //			printf("ans : %d
    ",ans); 
    	}
    	return ans%mod;
    }
    int main(){
    	len1=read();len2=read();
    	scanf("%s",s1+1);scanf("%s",s2+1);
    	if(len1<len2) return puts("0"),0;
    	KMP();
    	for(reg int i=1;i<=len1;i++) summ[i]+=summ[i-1];
    	for(reg int i=1;i<=len1;i++) summ[i]+=summ[i-1];
    	printf("%lld",manacher());
    	return 0;
    }
    
  • 相关阅读:
    工作感悟
    9/10记事
    总结几份工作的感悟
    四次原则
    在UC浏览器上很炫的一个效果
    php跨服务器传递对象
    wdlinux一键安装包
    手机号码4位隐藏
    php中英文字符串转字母转大小写
    MySQL添加用户、删除用户与授权
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13324750.html
Copyright © 2020-2023  润新知