zoukankan      html  css  js  c++  java
  • 【BZOJ4566】[HAOI2016] 找相同字符(重拾后缀自动机)

    点此看题面

    大致题意: 问两个字符串有多少个相同子串,此处认为位置不同、本质相同的子串是不同的方案。

    前言

    为什么要说重拾后缀自动机啊,明明就从未真正学会过好不好。。。

    大致思路

    考虑我们建出第一个串的后缀自动机,然后第二个串在第一个串的后缀自动机上跑匹配。

    我们枚举第二个串的每一个前缀,并假设当前匹配到了节点(p),对应第二个串中的长度为(k)

    则对于(p)的父节点及其所有祖先(列祖列宗,注意不包括(p)自身),都一定是第二个串的后缀。统计答案时应加上每一个节点本质不同的串的种数((len-len_{fa}))乘上包含这个后缀的子串个数((Sz)),这可以事先预处理。

    然后考虑(p)节点,由于(p)节点表示的是一个串的集合,而(len(p))可能比(k)大(即集合中部分串比(k)长),所以(p)节点上本质不同的串的种数应该是(k-len(fa_p)),同样乘上包含这个后缀的子串个数((Sz(p)))。

    具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define LL long long
    using namespace std;
    int n;char s1[N+5],s2[N+5];
    class SuffixAutomation
    {
    	private:
    		int Nt,lst,p[N<<1],t[N<<1];struct node {LL V;int Sz,F,L,S[30];}O[N<<1];
    	public:
    		I SuffixAutomation() {Nt=lst=1;}
    		I void Ins(CI x)//插入字符
    		{
    			RI p=lst,now=lst=++Nt;O[now].L=O[p].L+1,O[now].Sz=1;
    			W(p&&!O[p].S[x]) O[p].S[x]=now,p=O[p].F;if(!p) return (void)(O[now].F=1);
    			RI q=O[p].S[x];if(O[p].L+1==O[q].L) return (void)(O[now].F=q);
    			RI k=++Nt;(O[k]=O[q]).L=O[p].L+1,O[O[now].F=O[q].F=k].Sz=0;
    			W(p&&O[p].S[x]==q) O[p].S[x]=k,p=O[p].F;
    		}
    		I void Work()//预处理
    		{
    			RI i,x;for(i=1;i<=Nt;++i) ++t[O[i].L];for(i=1;i<=n;++i) t[i]+=t[i-1];
    			for(i=1;i<=Nt;++i) p[t[O[i].L]--]=i;for(i=Nt;i;--i) x=p[i],O[O[x].F].Sz+=O[x].Sz;//统计子树内串的个数
    			for(i=1;i<=Nt;++i) x=p[i],O[x].V=O[O[x].F].V+1LL*O[x].Sz*(O[x].L-O[O[x].F].L);//统计这个串及其祖先的答案
    		}
    		I void Solve(char *s)//第二个串跑匹配
    		{
    			LL res=0;for(RI i=1,l=strlen(s+1),p=1,k=0,x;i<=l;++i)//枚举字符
    			{
    				x=s[i]&31;W(p&&!O[p].S[x]) p=O[p].F;if(!p) {p=1,k=0;continue;}//往上跳找到一个有该儿子的节点
    				k=min(k,O[p].L)+1,p=O[p].S[x],res+=O[O[p].F].V+1LL*O[p].Sz*(k-O[O[p].F].L);//计算当前前缀的答案
    			}printf("%lld
    ",res);
    		}
    }S;
    int main()
    {
    	RI i;for(scanf("%s",s1+1),n=strlen(s1+1),i=1;i<=n;++i) S.Ins(s1[i]&31);
    	return S.Work(),scanf("%s",s2+1),S.Solve(s2),0;
    }
    
  • 相关阅读:
    求职经验丨应届毕业生,如何找到一份程序员工作呢?
    C++ 为什么能够流行几十年?C++ 之父为你揭晓答案!
    C/C++最大的祸害是什么?内存错误,舍它其谁!
    C语言丨关于结构体内存对齐,这份干货我收了,你随意~
    11月程序员平均工资又跌了?真是跌宕又起伏的一年!
    不熟悉C++面向对象?了解C++面向对象编程,看这篇够了!
    C++基础总结(六):C++进阶——模板和STL入门
    团队-团队编程项目作业名称-成员简介及分工
    Auto-close message box
    Adding a splash screen to your dialog based application
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4566.html
Copyright © 2011-2022 走看看