Codeforces 1073G Yet Another LCP Problem: 后缀数组+单调栈

本文探讨了在给定字符串上使用后缀数组和单调栈解决特定问题的方法,通过优化算法,实现了对大量询问的有效处理。介绍了如何利用单调栈统计两个字符串所有后缀之间的最长公共前缀之和,并针对特定问题进行算法改进,通过预处理和排序策略降低复杂度。

题意:

给定一个长度为 lenlenlen 的串 SSS , 每次询问给定一个长度为 kkkaaa 数组和一个长度为 lllbbb 数组,求:

∑i=1k∑j=1llcp(S[ai,len],S[bj,len])\sum_{i=1}^k\sum_{j=1}^llcp(S[a_i,len],S[b_j,len])i=1kj=1llcp(S[ai,len],S[bj,len]) len≤2⋅105len\le2\cdot10^5len2105

题解:

考虑一个弱化版的问题,给定两个字符串 S1,S2S_1,S_2S1,S2 ,求 ∑i=1len1∑j=1len2lcp(S1[i,len1],S2[j,len2])\sum_{i=1}^{len_1}\sum_{j=1}^{len_2}lcp(S_1[i,len_1],S_2[j,len_2])i=1len1j=1len2lcp(S1[i,len1],S2[j,len2]) .

即求两个串的所有后缀之间的组合的 lcplcplcp 之和. 容易发现,我们实际上可以把 S1,S2S_1,S_2S1,S2 连接起来跑后缀数组后,从头到尾扫描一遍,对于属于 S1S_1S1的后缀,添加到单调栈里面,同时维护和,而对于属于 S2S_2S2 的后缀用前面维护的 S1S_1S1 的和来更新 答案即可.这样就把从头到尾每一个 S2S_2S2 前面的 S1S_1S1 的贡献统计好了. 此时再反过来统计一次 S1S_1S1 前面的 S2S_2S2 的贡献, 即可得到答案. (把 poj3415poj 3415poj3415 的代码中的 KKK 改成 111 ,就可以解决这个问题.原因可以自己思考一下 ).

回到这个题目,弱化版的问题与本题 的差别在于, 弱化的问题统计的时候,每一个后缀一定: 要么属于用来更新 S2S_2S2S1S_1S1 ,要么属于用来更新 S1S_1S1S2S_2S2 ,但是现在题目要求统计的对数变成了数组里面的元素,因此需要判断一下,于是乎,我们把 poj3415poj 3415poj3415 的代码中的直接添加改成: 每一次添加进栈的元素判断是否属于一个数组里面的元素,统计答案的时候判断是否属于另一个数组里面的元素即可.

但是 ⋯\cdots ,我们交上去发现 TTT 了,我们仔细思考题目,发现这个题目中,询问次数非常多,但是我们每一次都用 O(len)O(len)O(len) 的方法跑了一次单调栈,复杂度很高.因此我们考虑怎么优化:

仔细想想,我们把弱化版的问题的代码, 修改了添加进栈和更新答案判断的部分. 但是没有变换的部分是每一次添加一个后缀,更新它对前面栈的影响.但由于这个题目实际上只在下标属于给定的数组元素时,才会添加进栈和更新答案,因此两个数组元素之间的部分,都是在用 height[i]height[i]height[i] 更新栈里面的元素,而连续的一段更新实际上可以用这一段里面的 heightheightheight 最小值去更新,因此,我们考虑排序后直接对数组里面的元素进行单调栈的操作,中间更新的部分直接预处理一段的 lcplcplcp 来更新.

ps:ps:ps: 此题给定的 S1,S2S_1,S_2S1,S2 是相同的,所以可以直接处理. 我们将数组元素更改一下,即可解决 S1,S2S_1,S_2S1,S2 不相同的情况.

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+7;
char s[maxn];
int sa[maxn],str[maxn],_rank[maxn],height[maxn];
int t1[maxn],t2[maxn],c[maxn];
int RMQ[maxn],mm[maxn],best[20][maxn],len;
typedef pair<int,int> pr;
bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int n,int m)
{
    int i,j,p,*x=t1,*y=t2;
    //第一轮基数排序,如果s的最大值很大,可以改为快速排序
    for(i=0;i<m;i++) c[i]=0;
    for(i=0;i<n;i++) c[x[i] = str[i]]++;
    for(i=1;i<m;i++) c[i]+=c[i-1];
    for(i=n-1;i>=0;i--) sa[--c[x[i]]] = i;
    for(j=1;j<=n;j<<=1)
    {
        p=0;
        for(i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++)  if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[y[i]]]++;
        for(i=1;i<m;i++) c[i]+=c[i-1];
        for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        //根据sa和x数组计算新的x数组
        swap(x,y);
        p=1; x[sa[0]]=0;
        for(i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p>=n) break;
        m=p;
    }
    int k=0; n--;
    for(i=0;i<=n;i++) _rank[sa[i]]=i;
    for(i=0;i<n;i++)
    {
        if(k) k--;
        j=sa[_rank[i]-1];
        while(str[i+k]==str[j+k]) k++;
        height[_rank[i]]=k;
    }
}
void initRMQ(int n){
    mm[0]=-1;
    for(int i=1;i<=n;i++)
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
    for(int i=1;i<=n;i++) best[0][i]=i;
    for(int i=1;i<=mm[n];i++)
        for(int j=1;j+(1<<i)-1<=n;j++){
            int a=best[i-1][j];
            int b=best[i-1][j+(1<<(i-1))];
            if(RMQ[a]<RMQ[b]) best[i][j]=a;
            else best[i][j]=b;
        }
}
int askRMQ(int a,int b){
    int t=mm[b-a+1];
    b-=(1<<t)-1;
    a=best[t][a];b=best[t][b];
    return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
    if(a==b) return len-a;
    a=_rank[a]; b=_rank[b];
    if(a>b) swap(a,b);
    return height[askRMQ(a+1,b)];
}
int main()
{
    int q,lim,x;
    scanf("%d%d",&len,&q); scanf("%s",s);
    for(int i=0;i<len;i++) str[i]=s[i];
    str[len]=0;da(len+1,256);
    for(int i=1;i<=len;i++) RMQ[i]=height[i];initRMQ(len);
    while(q--){
        int n,m; long long ans=0; vector<pr>Q;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d",&x),Q.emplace_back(x-1,0);//存储询问.
        for(int i=1;i<=m;i++) scanf("%d",&x),Q.emplace_back(x-1,1);
        sort(Q.begin(),Q.end(),[](const pr &L,const pr &R){//排序.
            if(L.first == R.first) return L.second < R.second;
            int l = lcp(L.first, R.first);
            return s[L.first + l] < s[R.first + l];
        }); int sz = Q.size();
        for(int k=0;k<2;k++){//正反都要算一次.
            long long tot=0; map<int,int>W;//用map代替单调栈.方便写.
            for(int i=0;i<sz;i++){
                int id=Q[i].first; int state=Q[i].second;
                if(state==k) ans+=tot;//如果是查询就更新答案.
                else W[len-id]++,tot+=len-id;//加入当前点后初始化(应该可以赋值为inf)并更新当前和.
                if(i<sz-1){//更新栈里面的元素.
                    int LCP = lcp(Q[i].first,Q[i+1].first);//这一步是关键,直接查询并更新这个区间内的height.
                    while(!W.empty()) {
                        auto it = --W.end();
                        if(it->first <= LCP) break;
                        tot -= 1LL*(it->first-LCP)*it->second;//更新差值.
                        W[LCP] += it->second;
                        W.erase(it);
                    }
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值