hdu 6988 / 2021“MINIEYE杯”中国大学生算法设计超级联赛(4)1004 Display Substring(后缀数组+二分)

https://acm.hdu.edu.cn/showproblem.php?pid=6988

题意:

给出一个字符串,每个字母都有一定的价值,子串的价值为各个字母价值总和

问所有不同的子串中,价值第k小的子串的价值是多少

首先二分一个价值,检验这个价值是否满足要求

一开始的二分写的 若小于等于二分值的子串数量<=k就更新答案,是错误的

应该是 若小于等于二分值的子串数量>=k就更新答案

因为可能存在多个不同的子串价值相同,不加他们不够k,加上他们之后超过k

如何求子串价值小于等于某个值的子串数量?

一个字符串的所有后缀的所有前缀就是所有的子串

如果没有不同子串的限制:

枚举后缀的起始位置,利用前缀和,二分可以求出这个后缀的多少个前缀价值<=二分的值

现在要求只计算不同的子串

利用后缀数组,按照rank从小到大算

因为排名相邻的两个串的最长公共前缀是height[i],也就是说排名为i的后缀的前height[i]个前缀都在排名为i-1的后缀里包含了

只需要对这个后缀二分的时候更改一下二分的下界即可

#include<bits/stdc++.h>

using namespace std;

#define N 100002

char ch[N];
int n,k,a[N],v[N],p,q,sa[2][N],rk[2][N],h[N];

long long m;
int w[30],val[N];

void mul(int *sa,int *rk,int *SA,int *RK)
{
    for(int i=1;i<=n;i++) v[rk[sa[i]]]=i;
    for(int i=n;i;i--)
        if(sa[i]>k) 
            SA[v[rk[sa[i]-k]]--]=sa[i]-k;
    for(int i=n-k+1;i<=n;i++) 
        SA[v[rk[i]]--]=i;
    for(int i=1;i<=n;i++) 
        RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]);
}
void presa()
{
    p=0;
    q=1;
    for(int i=1;i<=26;++i) v[i]=0;
    for(int i=1;i<=n;i++) v[a[i]]++;
    for(int i=1;i<=26;i++) v[i]+=v[i-1];
    for(int i=1;i<=n;i++) 
        sa[p][v[a[i]]--]=i;
    for(int i=1;i<=n;i++) 
        rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);
    for(k=1;k<n;k<<=1,swap(p,q))
        mul(sa[p],rk[p],sa[q],rk[q]);
    for(int i=1,k=0;i<=n;i++)
    {
        int j=sa[p][rk[p][i]-1];
        while(a[i+k]==a[j+k]) k++;
        h[rk[p][i]]=k;if(k) k--;
    }
}

long long check(int lim)
{
    int l,r=n,mid,tmp;
    long long sum=0;
    for(int i=1;i<=n;++i)
    {
        l=sa[p][i]+h[i];
        r=n;
        tmp=l-1;
        while(l<=r)
        {
            mid=l+r>>1;
            if(val[mid]-val[sa[p][i]-1]<=lim) 
            {
                tmp=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        sum+=tmp-(sa[p][i]+h[i])+1; 
    }
    return sum;
}

void solve()
{
    for(int i=1;i<=n;++i) val[i]=val[i-1]+w[a[i]];
    int l=0,r=1e7+1,mid,ans=-1;
    long long tmp;
    while(l<=r)
    {
        mid=l+r>>1;
        tmp=check(mid);
        if(tmp>=m) 
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    if(ans==1e7+1) ans=-1;
    printf("%d
",ans);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        scanf("%s",ch+1);
        for(int i=1;i<=n;++i) a[i]=ch[i]-'a'+1;
        for(int i=1;i<=26;++i) scanf("%d",&w[i]); 
        presa();
        solve();    
    }        
}
作者:xxy
本文版权归作者和博客园共有,转载请用链接,请勿原文转载,Thanks♪(・ω・)ノ。
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/15142098.html