第十五届浙江大学宁波理工学院程序设计大赛(同步赛)LCPS

好久没写题解了(其实是好久没做题了)。。

这题就是一个缝合怪题目,除了堆砌代码之外没有什么难度,所以我决定写一下关于回文树的东西。

对于长度为 n n n的字符串,回文树是可以在 O ( 26 n ) O(26n) O(26n)下构造出来的存储该串所有不同回文子串的数据结构。比较特殊的地方是其有两个根,一个根 R a R_a Ra表示长度为奇数的串,另一个根 R b R_b Rb表示长度为偶数的串。

在回文树中,从根到任意节点的路径上的字符构成的字符串表示了该节点存储的回文串的一半(如果长度为奇数,则 R a R_a Ra出发的边上的字符表示了中间的字符)。
换句话说,设当前节点为 v v v v v v表示的回文子串为 S S S v v v的一个孩子为 u u u,且边 ( v , u ) (v,u) (v,u)表示的字符为 a a a,那么 u u u表示的回文子串为 a S a aSa aSa。所以两个根表示的都是空串,为了方便,我们总是将 R a R_a Ra表示的串的长度设为 − 1 -1 1

在继续口胡之前,我们先直接扔出一个规律:一个长度为 n n n的字符串产生的不同的回文子串不会超过 n n n个。
这实际上是因为当我们为一个长度为 a a a的字符串末尾添加一个字符时,新产生的不同的回文子串只可能是包含第 a + 1 a+1 a+1个字符的最长的回文子串。容易证明,更短的回文子串必然已经在 [ 1 , a ] [1,a] [1,a]中出现过。

由这个规律,我们便有了构造回文树的方法:当我们处理到第 i i i个字符时,我们寻找 [ 1 , i ] [1,i] [1,i]中最长的后缀回文子串,如果这个子串没被处理过,我们便新建节点。同样由这个规律,我们知道回文树的空间也是 O ( 26 n ) O(26n) O(26n)的。
至于如何实现,我们只需要为每个节点维护一个指向其最长后缀回文子串的指针即可。每次更新,我们先找到最长后缀回文子串 A A A,新建节点(如果需要的话)之后,再找到第二长的后缀回文子串 B B B,由之前的规律, B B B必然已经被处理过,我们直接更新指针即可。
最后分析一下这么做的时间复杂度,设 f ( A ) f(A) f(A)表示字符串 A A A的后缀回文子串个数,容易得到 f ( a A a ) ≤ f ( A ) + 1 f(aAa)\le f(A)+1 f(aAa)f(A)+1,因为前者的一个后缀回文子串必然包括了后者的一个后缀回文子串(除了长度为 2 2 2的回文子串的情况,此时包括了后者的一个空子串,这也是那个多出来的 1 1 1的来历)。所以实际上每次更新, f f f的值最多只会增加1,所以对一个长度为 n n n的字符串构造一棵回文树的时间复杂度为 O ( 26 n ) O(26n) O(26n)

回到题目,只需要构造出两个字符串的回文树,取其交集,然后从叶子开始bfs找出最小字典序的回文串,然后在原串中找到第一个匹配点即可。

不知道这种缝合怪题目有什么意义。。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<climits>
#include<cstdio>
#include<random>
using namespace std;
//--Container
#include<queue>
typedef pair<int,int> pi;
//--
#define clr(a) memset(a,0,sizeof(a))
typedef long long ll;
const int up=2000000;
int n,nx[2][up+10][26],px[2][up+10],le[2][up+10],mx[up+10];char cz[2][up+10];
void _cl(int id){
    int i,j,t,tn,lst;clr(nx[id][0]),clr(nx[id][1]),px[id][1]=0,le[id][0]=-1,le[id][1]=0;
    for(tn=i=1,lst=0;i<=n;++i){
        for(t=cz[id][i]-'a',j=lst;cz[id][i]!=cz[id][i-le[id][j]-1];j=px[id][j]);
        if(nx[id][j][t])lst=nx[id][j][t];
        else{
            lst=++tn;clr(nx[id][tn]),le[id][tn]=le[id][j]+2,nx[id][j][t]=tn;
            for(j=px[id][j];cz[id][i]!=cz[id][i-le[id][j]-1];j=px[id][j]);
            px[id][tn]=le[id][tn]>1?nx[id][j][t]:1;
        }
    }
};
int dfs(int v,int u){
    int i,j,x=le[0][v];for(i=0;i<26;++i){
        if(nx[0][v][i]&&nx[1][u][i])x=max(x,dfs(nx[0][v][i],nx[1][u][i]));
        else
            nx[0][v][i]=0;
    }
    return mx[v]=x;
};
 
char ds[up+10],rs[up+10],fq[up+10];int dn,fqq[up+10],fdn[2],fn[2],fuck[2][up+10];bool bd[up+10];
 
void genpx(int n){
    int i,j;for(fqq[0]=fqq[1]=0,i=2;i<=n;++i){
        for(j=fqq[i-1];j&&rs[j+1]!=rs[i];j=fqq[j]);
        fqq[i]=rs[j+1]==rs[i]?j+1:0;
    }
};
int kksk(int x,char*ar){
    int i,j,t;for(i=j=1;;++i,++j){
        if(j==x&&rs[j]==ar[i])return i-x+1;
        if(rs[j]!=ar[i]){
            for(t=fqq[j-1];t&&rs[t+1]!=ar[i];t=fqq[t]);
            j=rs[t+1]==ar[i]?t+1:0;
        }
    }
    return 19260817;
};
 
void SBChuTiRenKaNiMaDe_log(int x,int&ra,int&rb){
    int i,j,k,d,t;genpx(x);
    ra=kksk(x,cz[0]),rb=kksk(x,cz[1]);
};
 
void dfx(int v,int x){
    int i,j;if(le[0][v]==x)fuck[0][fn[0]++]=v;
    for(i=0;i<26;++i)if(nx[0][v][i]&&mx[nx[0][v][i]]==x)dfx(nx[0][v][i],x),fqq[nx[0][v][i]]=v,fq[nx[0][v][i]]=i+'a';
};
 
void shitfs(int x){
    int i,j,k,d,a,b,t;for(i=0;i<=n;bd[i++]=0);fn[0]=fn[1]=0;
    dfx(x&1?0:1,x);for(fdn[a=0]=x/2+(x&1);;a^=1){
        if(!fdn[a])break;char z='z';for(i=0;i<fn[a];z=min(z,fq[fuck[a][i++]]));ds[fdn[a]]=z;
        for(fdn[a^1]=fdn[a]-1,fn[a^1]=0,i=0;i<fn[a];++i)if(fq[fuck[a][i]]==z&&!bd[fuck[a][i]]){
            fuck[a^1][fn[a^1]++]=fqq[fuck[a][i]];bd[fuck[a][i]]=1;
        }
    }
};
 
void cl(){
    int i,j,k,d,t;scanf("%d",&n);scanf("%s",cz[0]+1);scanf("%s",cz[1]+1);
    _cl(0),_cl(1);t=max(dfs(0,0),dfs(1,1));
    printf("%d\n",t);if(t){
        dn=t/2+(t&1);shitfs(t);
        for(i=t,j=dn;j;--i,--j)rs[i]=ds[j];
        for(i=1,j=dn;j;++i,--j)rs[i]=ds[j];
        rs[t+1]=0;SBChuTiRenKaNiMaDe_log(t,i,j);
        printf("%d %d\n%d %d\n",i,i+t-1,j,j+t-1);
    }
};
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif // ONLINE_JUDGE
    int t;scanf("%d",&t);while(t--)cl();
    return 0;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值