KMP,LCA(XJT Love Strings,玲珑杯 Round#8 A lonlife 1079)

6 篇文章 0 订阅
1 篇文章 0 订阅

官方题解错的,手动无视“深度”以及“dep”就对了。

一开始把前缀和后缀相同理解成了回文串,然后想到了用字典树+LCA,但是字典树太大了。。。然后就不会了。

如果我理解正确的话,应该是能很容易想到KMP+LCA的。

按照官方题解的说法,就是先对字符串求一下getfail,得到f[]数组,然后根据f[]数组对所有失配边各连一条边,就会形成一棵树。然后求两个前缀的最长公共前缀和最长公共后缀,就是找这两个前缀所对应节点的最近公共祖先,然后这个祖先节点所代表的前缀就是最长的公共前缀和后缀。输出这个前缀的长度(其实就是节点编号啦)即可。


为什么可以这样做呢?答案是KMP算法本质就是求各个前缀的最长前缀使得其等于这个前缀的等长的后缀。而这道题目不过是要求同时满足两个不同的前缀罢了。那么我们就可以不断地往失配边找,直到找到一个前缀能同时满足两个前缀的要求即可。如果用树建个模就是相当于求LCA啦。


代码

#include<bits/stdc++.h>
#define maxn 100010
using namespace std;

const int POW=18;
int d[maxn],p[maxn][POW];
vector<int>G[maxn];

void dfs(int u,int f)
{
    d[u]=d[f]+1;
    p[u][0]=f;
    for(int i=1;i<POW;i++) p[u][i]=p[p[u][i-1]][i-1];
    for(unsigned int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==f) continue;
        dfs(v,u);
    }
}

int lca(int a,int b)
{
    if(d[a]>d[b]) swap(a,b);
    int dist=d[b]-d[a];
    for(int i=0;i<POW;i++) if(dist&(1<<i)) b=p[b][i];
    if(a==b) return a;
    else for(int i=POW-1;i>=0;i--)
        if(p[a][i]!=p[b][i])
            a=p[a][i],b=p[b][i];
    return p[a][0];
}

char s[maxn];
int f[maxn];
int l;

void getfail()
{
    f[0]=f[1]=0;
    for(int i=1;i<l;i++)
    {
        int j=f[i];
        while(j&&s[i]!=s[j]) j=f[j];
        f[i+1]=s[i]==s[j]?j+1:0;
    }
    /*for(int i=0;i<=l;i++) printf("%d",f[i]);
    puts("");*/
}

int kase;

void add(int u,int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s);
        l=strlen(s);
        for(int i=1;i<=l+1;i++) G[i].clear();
        getfail();
        for(int i=1;i<=l;i++)
            add(i+1,f[i]+1);
        dfs(1,0);
        int q,u,v;
        scanf("%d",&q);
        printf("Case #%d:\n",++kase);
        while(q--)
        {
            scanf("%d %d",&u,&v);
            u++,v++;
            int w=lca(u,v);
            if(w==u||w==v)
            {
                int ans=p[w][0]-1;
                if(ans<0) ans=0;
                printf("%d\n",ans);
            }
            else printf("%d\n",w-1);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值