HDU5853 Jong Hyok and String(二分 + 后缀数组)

题目

Source

http://acm.hdu.edu.cn/showproblem.php?pid=5853

Description

Jong Hyok loves strings. One day he gives a problem to his friend you. He writes down n strings Pi in front of you, and asks m questions. For i-th question, there is a string Qi. We called strange set(s) = {(i, j) | s occurs in Pi and j is the position of its last character in the current occurence}. And for ith question, you must answer the number of different strings t which satisfies strange set(Qi) = strange set(t) and t is a substring of at least one of the given n strings.

Input

First line contains T, a number of test cases.

For each test cases, there two numbers n, m and then there are n strings Pi and m strings Qj.(i = 1…n, j = 1…m)

1 <= T <= 10
1 <= n <= 100000
1 <= m<= 500000
1 <=|Pi|<=100000
1 <=|Qi|<=100000
∑ni=1|Pi|≤100000
File size is less than 3.5 megabytes.

Output

For each test case, first line contains a line “Case #x:”, x is the number of the case.

For each question, you should print one integer in one line.

Sample Input

1
2 2
aba
ab
a
ab

Sample Output

Case #1:
1
2

分析

题目大概说给若干的字符串pi,然后若干个询问,询问pi内有多少个不同子串与给定的询问字符串的strange set相同。一个字符串的strange set是一个二元组(i,j)的集合,表示该字符串在pi中出现且最后一个字符在pi中的位置j。

这题比赛时和队友讨论了挺久的。

首先想到的是,与查询串的strange set相同一定是查询串的后缀(其实不止是这样= =)。而查询串后缀的strange set不与查询串相同的情况是这个后缀在pi中被匹配了,但在那个位置查询串没被匹配。

然后队友考虑到通过把串反转,将后缀转化成前缀。

接下去,看到Σ|pi|<=100000,所以开始往后缀数组上面想。自然,那些pi要反转(这时考虑的是前缀了),然后拼接起来,中间用特殊字符隔开。

而求得其各个后缀排序后,对于任何一个模式串是能通过二分去查找到它所在匹配位置。然后就开始考虑对于查询串的各个前缀,去通过二分其位置的上下界求得有多少个与其匹配,然后再与查询串匹配次数对比,如果相等说明该前缀是可行的。

不过时间复杂度显然不行。后面我想到如果前缀x不行,那么前缀x-1也一定不行,然后慢慢地得出了这个结论——

  • 对于各个查询串,通过两次二分,找到它匹配的上界upp和下界low(upp<=low。。),那么结果就是|查询串|-max(LCP(upp,upp-1),LCP(low,low+1))!

我们验证了时间复杂度,是所有查询串总长*Σ|pi|*logΣ|pi|,所有查询串总长Clarification说到200W左右,那样大概是可以一试的。于是就写了,不过WA= =二分改了改,然后什么什么。。比赛结束也没搞出来。

其实,一开始逻辑就有漏洞了。。【与查询串的strange set相同一定是查询串的后缀(其实不止是这样= =)】,还有一种情况!

比如这个数据:

1 1
bbbaa
bba

结果应该是3,因为:

  • strange set(“bba”) = {(1,4)}
  • bba的这两个后缀满足:strange set(“bba”) = {(1,4)}、strange set(“ba”) = {(1,4)}
  • 此外还有这个满足:strange set(“bbba”) = {(1,4)}

就是说还有包含整个字符串的可能满足。然后我想了想,画了画,又得出结论:

  • 这种情况的数量就是上下界的LCP长度减去查询串的长度!

另外要注意上界=下界的情况,还有特殊字符在这儿应该要互不相同。。

感觉这题好难描述= =就这样吧。。最后我终于AC了。。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF (1<<30)
#define MAXN 222222

int wa[MAXN],wb[MAXN],wv[MAXN],ws[MAXN];
int cmp(int *r,int a,int b,int l){
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int sa[MAXN],rnk[MAXN],height[MAXN];
void SA(int *r,int n,int m){
    int *x=wa,*y=wb;

    for(int i=0; i<m; ++i) ws[i]=0;
    for(int i=0; i<n; ++i) ++ws[x[i]=r[i]];
    for(int i=1; i<m; ++i) ws[i]+=ws[i-1];
    for(int i=n-1; i>=0; --i) sa[--ws[x[i]]]=i;

    int p=1;
    for(int j=1; p<n; j<<=1,m=p){
        p=0;
        for(int i=n-j; i<n; ++i) y[p++]=i;
        for(int i=0; i<n; ++i) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(int i=0; i<n; ++i) wv[i]=x[y[i]];
        for(int i=0; i<m; ++i) ws[i]=0;
        for(int i=0; i<n; ++i) ++ws[wv[i]];
        for(int i=1; i<m; ++i) ws[i]+=ws[i-1];
        for(int i=n-1; i>=0; --i) sa[--ws[wv[i]]]=y[i];
        swap(x,y); x[sa[0]]=0; p=1;
        for(int i=1; i<n; ++i) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }

    for(int i=1; i<n; ++i) rnk[sa[i]]=i;
    int k=0;
    for(int i=0; i<n-1; height[rnk[i++]]=k){
        if(k) --k;
        for(int j=sa[rnk[i]-1]; r[i+k]==r[j+k]; ++k);
    }
}

int st[18][MAXN];
void ST(int *a,int n){
    for(int i=1; i<=n; ++i) st[0][i]=a[i];
    for(int i=1; i<18; ++i){
        for(int j=1; j<=n; ++j){
            if(j+(1<<i)>n) break;
            st[i][j]=min(st[i-1][j],st[i-1][j+(1<<i-1)]);
        }
    }
}
int rmq(int a,int b){
    if(a>b) swap(a,b);
    int k=(int)(log2(b-a+1)+1e-6);
    return min(st[k][a],st[k][b-(1<<k)+1]);
}

char str[MAXN];
int an,a[MAXN],b[MAXN],bn;
int len[MAXN];

int cmp(int k){
    int i;
    for(i=0; i+k<an && i<bn; ++i){
        if(a[i+k]>b[i]) return 1;
        else if(a[i+k]<b[i]) return -1;
    }
    if(i!=bn) return -1;
    return 0;
}

int main(){
    int t,n,m;
    scanf("%d",&t);
    for(int cse=1; cse<=t; ++cse){
        scanf("%d%d",&n,&m);
        an=0;
        for(int i=0; i<n; ++i){
            scanf("%s",str);
            for(int j=strlen(str)-1; j>=0; --j){
            	len[an]=j+1;
                a[an++]=str[j]-‘a‘+1;
            }
            a[an++]=28+i;
        }
        a[an++]=0;
        SA(a,an,28+n);
        ST(height,an-1);
        printf("Case #%d:\n",cse);
        while(m--){
            scanf("%s",str);
            bn=0;
            for(int j=strlen(str)-1; j>=0; --j){
                b[bn++]=str[j]-‘a‘+1;
            }

            int l=1,r=an-1;
            int upp=-1;
            while(l<=r){
                int mid=l+r>>1;
                int tmp=cmp(sa[mid]);
                if(tmp==0){
                    upp=mid;
                    r=mid-1;
                }else if(tmp>0) r=mid-1;
                else if(tmp<0) l=mid+1;
            }
            if(upp==-1){
                printf("%d\n",0);
                continue;
            }
            l=1,r=an-1;
            int low=-1;
            while(l<=r){
                int mid=l+r>>1;
                int tmp=cmp(sa[mid]);
                if(tmp==0){
                    low=mid;
                    l=mid+1;
                }else if(tmp>0) r=mid-1;
                else if(tmp<0) l=mid+1;
            }
            int tmp=0;
            if(upp!=1){
                tmp=max(tmp,height[upp]);
            }
            if(low!=an-1){
                tmp=max(tmp,height[low+1]);
            }
            if(upp==low) printf("%d\n",bn-tmp+len[sa[upp]]-bn);
            else printf("%d\n",bn-tmp+rmq(upp+1,low)-bn);
        }
    }
    return 0;
}
时间: 2024-10-27 05:41:48

HDU5853 Jong Hyok and String(二分 + 后缀数组)的相关文章

hdu 5853 Jong Hyok and String(广义后缀自动机)

题目链接:hdu 5853 Jong Hyok and String 题意: 给你n个字符串,m个询问,每次询问一个字符串 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t串的数量. 题解: 如果是n=1,那么就是后缀自动机的一道裸题,答案就是Qi串匹配的最后一个节点x,ml[x]-ml[f[x]]. 现在是多个串,那么就建立一个广义后缀自动机.每次插入一个串后,将last=root,然后继续插下一个就行了. 最

hdu 5008(2014 ACM/ICPC Asia Regional Xi&#39;an Online ) Boring String Problem(后缀数组&amp;二分)

Boring String Problem Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 219    Accepted Submission(s): 45 Problem Description In this problem, you are given a string s and q queries. For each que

Hdu 5030 Rabbit&#39;s String (后缀数组)

题目大意: 要求将一个长串分解成最多k个子串,使得分开的n个串的字典序最大的那一个子串的字典序最小. 思路分析: 要最大的最小,不难想到二分的. 我们二分出原串中的第rk大子串就是目标串. 现在就是怎么判断这个串满足要求,也就是我们如何分其他部分,使之成为字典序最大的一个. 我们可以通过rk轻易的找到这是哪一个串,假设它处在sa[t]中. 那么可以知道 在 sa数组中t以前的子串的字典序都是比目标串小的. 而后面会有比sa大的,我们就要分解这些串. 我们从t 扫描 到n的height  ,如果有

hdu 5008 Boring String Problem(后缀数组)

题目链接:hdu 5008 Boring String Problem 题目大意:给定一个字符串,初始状态l,r为0,每次询问子串中字典序第l^r^v+1的子串区间,对于重复的输出下标小的. 解题思路:后缀数组,对给定字符串做后缀数组,然后根据height数组确定每个位置做为起点的子串有多少,然后二分查找确定起点位置,但是因为子串的重复的要输出下表小的,所以确定起点后还要确定字典序最小的下标. #include <cstdio> #include <cstring> #includ

POJ3294 Life Forms(二分+后缀数组)

给n个字符串,求最长的多于n/2个字符串的公共子串. 依然是二分判定+height分组. 把这n个字符串连接,中间用不同字符隔开,跑后缀数组计算出height: 二分要求的子串长度,判断是否满足:height分组,统计一个组不同的字符串个数是否大于n/2: 最后输出方案,根据二分得出的子串长度的结果,直接再遍历一遍height,因为这儿是有序的后缀所以找到一个就直接输出. 1 #include<cstdio> 2 #include<cstring> 3 #include<cm

zoj 1905 Power String(后缀数组)

 题意:给定一个字符串L,已知这个字符串是由某个字符串S 重复R 次而得到的,求R 的最大值. 做法比较简单,穷举字符串S 的长度k,然后判断是否满足.判断的时候, 先看字符串L 的长度能否被k 整除,再看suffix(0)和suffix(k)的最长公共 前缀是否等于n-k.在询问最长公共前缀的时候,suffix(0)是固定的,所以RMQ 问题没有必要做所有的预处理, 只需求出height 数组中的每一个数到 height[rank[0]]之间的最小值即可.整个做法的时间复杂度为O(nlog

HDU - 5008 Boring String Problem (后缀数组+二分+RMQ)

Problem Description In this problem, you are given a string s and q queries. For each query, you should answer that when all distinct substrings of string s were sorted lexicographically, which one is the k-th smallest. A substring si...j of the stri

HDU 5008 Boring String Problem(后缀数组+二分)

题目链接 思路 想到了,但是木写对啊....代码 各种bug,写的乱死了.... 输出最靠前的,比较折腾... #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <cmath> #include <map> using namespace std; #define N 501000 #define LL __in

HDU5008 Boring String Problem(后缀数组)

练习一下字符串,做一下这道题. 首先是关于一个字符串有多少不同子串的问题,串由小到大排起序来应该是按照sa[i]的顺序排出来的产生的. 好像abbacd,排序出来的后缀是这样的 1---abbacd     第一个串产生的6个前缀都是新的子串 2---acd          第二个串除了和上一个串的前缀1 3-1=2 产生了2个子串 3---bacd        4-0=4 4---bbacd      5-1=4 5---cd           2-0=0 6---d