zoukankan      html  css  js  c++  java
  • poj 3415 Common Substrings(后缀数组+单调栈)

    http://poj.org/problem?id=3415

    Common Substrings
    Time Limit: 5000MS   Memory Limit: 65536K
    Total Submissions: 5805   Accepted: 1911

    Description

    A substring of a string T is defined as:

    T(i, k)=TiTi+1...Ti+k-1, 1≤ii+k-1≤|T|.

    Given two strings A, B and one integer K, we define S, a set of triples (i, j, k):

    S = {(i, j, k) | kK, A(i, k)=B(j, k)}.

    You are to give the value of |S| for specific A, B and K.

    Input

    The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.

    1 ≤ |A|, |B| ≤ 105 1 ≤ K  min{|A|, |B|} Characters of A and B are all Latin letters.

    Output

    For each case, output an integer |S|.

    Sample Input

    2
    aababaa
    abaabaa
    1
    xx
    xx
    0
    

    Sample Output

    22
    5

    思路:

    【题意】

    给定一个数k

    在给定两个字符串A,B

    求三元组(i,j,k)(表示从A的第i位起和从B的j位起长度为k的字符串相同)的个数

    【输入】

    多组数据

    每组数据第一行为k

    接下来两行分别为A、B(长度均不大于100000)

    【输出】

    对于每组数据,输出一个数,表示三元组的个数

     

    后缀数组应用题之一

    后缀数组的用法很经典

    将两个字符串之间加一个没出现过的字符连接起来

    然后求height

    对于B的一个后缀,对应每一个A的后缀若他们的公共前缀长为l,若l大于等于k,则会有l-k+1种三元组

    这个统计便是本题的难点

    如果枚举A的后缀和B的后缀,那么复杂度为n^2,对于本题明显是不可以的

    所以要另寻途径

    根据论文的提示要使用单调栈,我想到了一种实现

    按rank的顺序,统计每一个B的后缀名次之前的A的后缀有关的三元组数量

    然后再统计每一个A的后缀名次之前的B的后缀有关的三元组数量

    两者之和便是答案

    将问题划分为了两个等价的问题

    那么完成每一个问题的时候从左到右扫描,每一个只跟已扫描过的有关

    这时候便可以动态维护了

    首先,与许多后缀数组题目中类似的,这里存在三元组的后缀们是聚集在一起的

    按height可以分组

    对于每一组从左到右扫描,则需要维护一个栈和一个值

    这个栈是栈内元素与当前元素公共前缀长度递增的一个栈,值是若当前当前后缀之前的三元组个数

    根据最长公共前缀的性质,rank越相近,则公共前缀长度越大,所以从左到右扫描的后缀与当前后缀的公共前缀长度是递减的

    栈内每个元素表示之前有total个后缀与当前元素公共前缀长度为sim,明显,若当前height小于某些栈内元素的sim,则需要修改这些元素和值

    据此调整,每个元素最多进出栈一次,复杂度为O(N)

    题解部分转自:http://blog.csdn.net/weixinding/article/details/7222882

    AC代码:(用数组模拟栈)

      1 #include <iostream>
      2 #include <stdio.h>
      3 #include<string.h>
      4 
      5 #define maxn 200010
      6 
      7 #define cls(x) memset(x, 0, sizeof(x))
      8 
      9 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
     10 
     11 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
     12 
     13 //倍增算法
     14 void da(char *r,int *sa,int n,int m)
     15 {
     16      cls(wa);
     17      cls(wb);
     18      cls(wv);
     19      int i,j,p,*x=wa,*y=wb,*t;
     20 
     21      //基数排序
     22      for(i=0;i<m;i++) wss[i]=0;
     23      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
     24      for(i=1;i<m;i++) wss[i]+=wss[i-1];
     25      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
     26 
     27      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
     28      for(j=1,p=1;p<n;j*=2,m=p)
     29      {
     30        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
     31        for(p=0,i=n-j;i<n;i++) y[p++]=i;
     32        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
     33 
     34        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
     35        for(i=0;i<n;i++) wv[i]=x[y[i]];
     36        for(i=0;i<m;i++) wss[i]=0;
     37        for(i=0;i<n;i++) wss[wv[i]]++;
     38        for(i=1;i<m;i++) wss[i]+=wss[i-1];
     39        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
     40 
     41        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
     42        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
     43        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
     44      }
     45 }
     46 
     47 int rank[maxn],height[maxn];
     48 
     49 //得到height数组:排名相邻的两个后缀的最长公共前缀
     50 void calheight(char *r,int *sa,int n)
     51 {
     52      cls(rank);
     53      cls(height);
     54      int i,j,k=0;
     55      for(i=1;i<n;i++) rank[sa[i]]=i;
     56      for(i=0;i<n;height[rank[i++]]=k)
     57          for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
     58      return;
     59 }
     60 
     61 char ca[maxn];
     62 int sa[maxn];
     63 int N,len;
     64 int minh[maxn];
     65 int stk[maxn],cnt[maxn];
     66 
     67 int main()
     68 {
     69     int i,k;
     70     while (~scanf("%d",&k)&&k)
     71     {
     72         scanf("%s",ca);
     73         len= N = strlen(ca);
     74         ca[N] = '#';
     75         scanf("%s",ca+N+1);
     76         N = strlen(ca);
     77         da(ca, sa, N, 130);
     78         calheight(ca,sa,N);
     79         for(i=1;i<=N;i++)  //将LCP-k+1预处理到height数组中
     80         {
     81             if(height[i]>=k)   height[i]-=k-1;
     82             else   height[i]=0;
     83         }
     84         __int64 ans,temp,size;
     85         ans=temp=0;
     86         int top=-1;
     87         for(i=1;i<=N;i++)
     88         {
     89             for(size=0;top>-1&&stk[top]>height[i];top--)
     90             {
     91                 size+=cnt[top];
     92                 temp+=(height[i]-stk[top])*cnt[top];
     93             }
     94             stk[++top]=height[i];
     95             cnt[top]=size;
     96             if(sa[i-1]<len)
     97             {
     98                 temp+=height[i];
     99                 cnt[top]++;
    100             }
    101             if(sa[i]>len)
    102                 ans+=temp;
    103         }
    104         temp=0;
    105         top=-1;
    106         for(i=1;i<=N;i++)
    107         {
    108             for(size=0;top>-1&&stk[top]>height[i];top--)
    109             {
    110                 size+=cnt[top];
    111                 temp+=(height[i]-stk[top])*cnt[top];
    112             }
    113             stk[++top]=height[i];
    114             cnt[top]=size;
    115             if(sa[i-1]>len)
    116             {
    117                 temp+=height[i];
    118                 cnt[top]++;
    119             }
    120             if(sa[i]<len)
    121                 ans+=temp;
    122         }
    123         printf("%I64d
    ",ans);
    124     }
    125     return 0;
    126 }

    附:

    利用栈容器TLE(搞不懂为神马!!!):

    <过了两天,终于被我发现为嘛超时了,G++提交AC,C++提交TLE,我了个去啊,坑爹,坑爹啊,目测原因C++容器处理过程耗时太多了>

    (C++)TLE代码<G++提交AC>:

      1 #include <iostream>
      2 #include <stdio.h>
      3 #include<string.h>
      4 #include <stack>
      5 
      6 using namespace std;
      7 
      8 #define maxn 200010
      9 
     10 #define cls(x) memset(x, 0, sizeof(x))
     11 
     12 struct Nod
     13 {
     14     int height;
     15     int s;
     16 }node;
     17 
     18 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
     19 
     20 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
     21 
     22 //倍增算法
     23 void da(char *r,int *sa,int n,int m)
     24 {
     25      cls(wa);
     26      cls(wb);
     27      cls(wv);
     28      int i,j,p,*x=wa,*y=wb,*t;
     29 
     30      //基数排序
     31      for(i=0;i<m;i++) wss[i]=0;
     32      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
     33      for(i=1;i<m;i++) wss[i]+=wss[i-1];
     34      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
     35 
     36      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
     37      for(j=1,p=1;p<n;j*=2,m=p)
     38      {
     39        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
     40        for(p=0,i=n-j;i<n;i++) y[p++]=i;
     41        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
     42 
     43        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
     44        for(i=0;i<n;i++) wv[i]=x[y[i]];
     45        for(i=0;i<m;i++) wss[i]=0;
     46        for(i=0;i<n;i++) wss[wv[i]]++;
     47        for(i=1;i<m;i++) wss[i]+=wss[i-1];
     48        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
     49 
     50        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
     51        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
     52        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
     53      }
     54 }
     55 
     56 int rank[maxn],height[maxn];
     57 
     58 //得到height数组:排名相邻的两个后缀的最长公共前缀
     59 void calheight(char *r,int *sa,int n)
     60 {
     61      cls(rank);
     62      cls(height);
     63      int i,j,k=0;
     64      for(i=1;i<n;i++) rank[sa[i]]=i;
     65      for(i=0;i<n;height[rank[i++]]=k)
     66          for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
     67      return;
     68 }
     69 
     70 char ca[maxn];
     71 int sa[maxn];
     72 int N,len;
     73 int minh[maxn];
     74 
     75 int main()
     76 {
     77     int i,k;
     78     while (~scanf("%d",&k)&&k)
     79     {
     80         scanf("%s",ca);
     81         len= N = strlen(ca);
     82         ca[N] = '#';
     83         scanf("%s",ca+N+1);
     84         N = strlen(ca);
     85         da(ca, sa, N, 130);
     86         calheight(ca,sa,N);
     87         for(i=1;i<=N;i++)  //将LCP-k+1预处理到height数组中
     88         {
     89             if(height[i]>=k)   height[i]-=k-1;
     90             else   height[i]=0;
     91         }
     92         stack<Nod> stk;
     93         __int64 ans,temp;
     94         int s=0;
     95         ans=0;
     96         temp=0;
     97         for(i=1;i<=N;i++)  //这里从1遍历到N 等号不能去掉,需要保证pop能彻底
     98         {
     99             s=0;
    100             while(stk.size()>0&&height[i]<stk.top().height)
    101             {
    102                 s+=stk.top().s;
    103                 temp+=(height[i]-stk.top().height)*stk.top().s;
    104                 stk.pop();
    105             }
    106             if(sa[i-1]<len)
    107             {
    108                 temp+=height[i];
    109                 s++;
    110             }
    111             if(sa[i]>len)   ans+=temp;
    112             node.height = height[i];
    113             node.s = s;
    114             stk.push(node);
    115         }
    116         while(!stk.empty())  stk.pop();
    117         temp=0;
    118         for(i=1;i<=N;i++)
    119         {
    120             s=0;
    121             while(stk.size()>0&&height[i]<stk.top().height)
    122             {
    123                 s+=stk.top().s;
    124                 temp+=(height[i]-stk.top().height)*stk.top().s;
    125                 stk.pop();
    126             }
    127             if(sa[i-1]>len)
    128             {
    129                 temp+=height[i];
    130                 s++;
    131             }
    132             if(sa[i]<len)   ans+=temp;
    133             node.height = height[i];
    134             node.s = s;
    135             stk.push(node);
    136         }
    137         while(!stk.empty())  stk.pop();
    138         printf("%I64d
    ",ans);
    139     }
    140     return 0;
    141 }

     

  • 相关阅读:
    Java API之时间、异常和集合
    JAVA API 之包装类、数学类和 Calendar 类
    regular expression ---正则表达式 --- REGEX 的一些补充
    正则表达式
    JavaScript 的一些应用场景分析
    JavaScript 简介
    ERROR internal error: process exited while connecting to monitor
    常用服务默认端口号
    shell笔记
    php登录注册
  • 原文地址:https://www.cnblogs.com/crazyapple/p/3211931.html
Copyright © 2011-2022 走看看