LibreOJ #6041.「雅礼集训 2017 Day7」事情的相似度 后缀自动机+set启发式合并+扫面线+树状数组

25 篇文章 0 订阅
24 篇文章 0 订阅

题意

给一个长度为n的01串,每次询问给出一个区间[l,r],问所有结束位置在[l,r]中的前缀,两两lcs的最大值是多少。
n,m105 n , m ≤ 10 5

分析

显然一个前缀对(x,y)能贡献到所有满足 lx,yr l ≤ x , y ≤ r 的询问 [l,r] [ l , r ]
两个前缀的lcs等价于其在sam的parents树上lca的mx。那么我们可以先把sam建出来,用set维护当前点的right集,然后从底往上启发式合并。
每次合并两个set的时候,把所有新加入的相邻点对取出来,权值是当前点的mx,那么就只有 O(nlogn) O ( n l o g n ) 个点对会造成贡献。
剩下的就是一个二维数点问题,用扫描线+树状数组来搞就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<set>

const int N=100005;

int n,m,sz,ls,b[N*2],c[N*2],t[N],ch[N*2][2],mx[N*2],tot,fa[N*2],ans[N];
char str[N];
struct data{int x,y,id;}a[N*30],q[N];
std::set<int> se[N*2];
std::set<int>::iterator it,it1;

void extend(int x)
{
    int p,q,np,nq;
    p=ls;ls=np=++sz;mx[np]=mx[p]+1;
    for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;
    if (!p) fa[np]=1;
    else
    {
        q=ch[p][x];
        if (mx[q]==mx[p]+1) fa[np]=q;
        else
        {
            nq=++sz;mx[nq]=mx[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq]=fa[q];fa[q]=fa[np]=nq;
            for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
        }
    }
}

void pre()
{
    for (int i=1;i<=sz;i++) b[mx[i]]++;
    for (int i=1;i<=sz;i++) b[i]+=b[i-1];
    for (int i=sz;i>=1;i--) c[b[mx[i]]--]=i;
    for (int i=sz;i>=2;i--)
    {
        int x=c[i],y=fa[x];
        if (se[x].size()>se[y].size()) std::swap(se[x],se[y]);
        for (it=se[x].begin();it!=se[x].end();it++)
        {
            int w=*it;
            it1=se[y].lower_bound(w);
            if (it1!=se[y].end()) a[++tot]=(data){w,*it1,mx[y]};
            if (it1!=se[y].begin()) it1--,a[++tot]=(data){*it1,w,mx[y]};
        }
        for (it=se[x].begin();it!=se[x].end();it++) se[y].insert(*it);
    }
}

bool cmp(data a,data b)
{
    return a.x>b.x;
}

void ins(int x,int y)
{
    while (x<=n) t[x]=std::max(t[x],y),x+=x&(-x);
}

int query(int x)
{
    int ans=0;
    while (x) ans=std::max(ans,t[x]),x-=x&(-x);
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",str+1);
    ls=sz=1;
    for (int i=1;i<=n;i++) extend(str[i]-'0'),se[ls].insert(i);
    pre();
    std::sort(a+1,a+tot+1,cmp);
    for (int i=1;i<=m;i++) scanf("%d%d",&q[i].x,&q[i].y),q[i].id=i;
    std::sort(q+1,q+m+1,cmp);
    int p=1;
    for (int i=1;i<=m;i++)
    {
        while (p<=tot&&a[p].x>=q[i].x) ins(a[p].y,a[p].id),p++;
        ans[q[i].id]=query(q[i].y);
    }
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值