• POJ 3415 Common Substrings(长度不小于K的公共子串的个数+后缀数组+height数组分组思想+单调栈)


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

    题意:
    求长度不小于K的公共子串的个数。

    思路:
    好题!!!拉丁字母让我Wa了好久!!单调栈又让我理解了好久!!太弱啊!!

    最简单的就是暴力枚举,算出LCP,那么这个LCP对答案的贡献就是$x-k+1$。

    我们可以将height进行分组,大于等于k的在同一组,如果两个后缀的最长公共子串>=k,那么它们肯定在同一个组内。现在从头开始扫,每遇到A的后缀时,就统计一下它和它前面的B的后缀能组成多少长度>=k的公共子串,然后再反过来处理B的后缀即可,一共需要扫两遍。但是这样的时间复杂度是O(n^2),是行不通的。

    因为两个后缀的LCP是它们之间的最小height值,所以可以维护一个自底向上递增的单调栈,如果有height值小于了当前栈顶的height值,那么大于它的那些只能按照当前这个小的值来计算。这样分别处理两次,一次处理A的后缀,一次处理B的后缀。需要记录好栈里的和以及每个组内的后缀数。

      1 #include<iostream>
      2 #include<algorithm>
      3 #include<cstring>
      4 #include<cstdio>
      5 #include<vector>
      6 #include<stack>
      7 #include<queue>
      8 #include<cmath>
      9 #include<map>
     10 #include<set>
     11 using namespace std;
     12 typedef long long ll;
     13 typedef pair<int,int> pll;
     14 const int INF = 0x3f3f3f3f;
     15 const int maxn=2*1e5+5;
     16 
     17 int n;
     18 char s[maxn],s1[maxn];
     19 int sa[maxn],t[maxn],t2[maxn],c[maxn];
     20 int Rank[maxn],height[maxn];
     21 int sta[maxn],cnt[maxn];
     22 
     23 void build_sa(int m)
     24 {
     25     int *x=t,*y=t2;
     26     //基数排序
     27     for(int i=0;i<m;i++)    c[i]=0;
     28     for(int i=0;i<n;i++)    c[x[i]=s[i]]++;
     29     for(int i=1;i<m;i++)    c[i]+=c[i-1];
     30     for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
     31     for(int k=1;k<=n;k<<=1)
     32     {
     33         int p=0;
     34         //直接利用sa数组排序第二关键字
     35         for(int i=n-k;i<n;i++)  y[p++]=i;
     36         for(int i=0;i<n;i++)    if(sa[i]>=k)    y[p++]=sa[i]-k;
     37         //基数排序第一关键字
     38         for(int i=0;i<m;i++)    c[i]=0;
     39         for(int i=0;i<n;i++)    c[x[y[i]]]++;
     40         for(int i=1;i<m;i++)    c[i]+=c[i-1];
     41         for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
     42         //根据sa和y计算新的x数组
     43         swap(x,y);
     44         p=1;
     45         x[sa[0]]=0;
     46         for(int i=1;i<n;i++)
     47             x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
     48         if(p>=n)
     49             break;
     50         m=p;                //下次基数排序的最大值
     51     }
     52 }
     53 
     54 void getHeight(int n)
     55 {
     56     int i,j,k=0;
     57     for(i=1;i<=n;i++)  Rank[sa[i]]=i;
     58     for(i=0;i<n;i++)
     59     {
     60         if(k)  k--;
     61         int j=sa[Rank[i]-1];
     62         while(s[i+k]==s[j+k])  k++;
     63         height[Rank[i]]=k;
     64     }
     65 }
     66 
     67 int main()
     68 {
     69     int k;
     70     //freopen("in.txt","r",stdin);
     71     while(~scanf("%d",&k))
     72     {
     73         if(!k)  break;
     74         scanf("%s%s",s,s1);
     75         int len1 = strlen(s);
     76         int len2 = strlen(s1);
     77         s[len1]='@';
     78         for(int i=0;i<len2;i++)  s[len1+1+i]=s1[i];
     79         s[len1+len2+1]='*';
     80         s[len1+len2+2]='';
     81         n=strlen(s);
     82         build_sa(300);
     83         getHeight(n-1);
     84 
     85         ll sum=0,ans=0;  //sum代表栈里对答案的贡献值,cnt数组记录的是一个组内的后缀数
     86         int top=0;
     87         for(int i=1;i<n;i++)   //处理A和它前面的B的公共前缀
     88         {
     89             if(height[i]<k)  {top=0;sum=0;continue;}
     90             int num=0;
     91             while(top && sta[top]>height[i])
     92             {
     93                 sum-=(ll)(sta[top]-k+1)*cnt[top];  //在这儿比height[i]大的都要变成height[i],所以先减去先前加上去的
     94                 sum+=(ll)(height[i]-k+1)*cnt[top]; //乘cnt[top]的原因是可能有多个合并成一个了,比如4,5,3,那么4和5都只能以3来计算
     95                 num+=cnt[top];                     //那么此时这三个可以合并在一起,cnt个数就是3,和就是3*3=9
     96                 top--; 
     97             }
     98             sta[++top]=height[i];
     99             if(sa[i-1]>len1)
    100             {
    101                 sum+=(ll)height[i]-k+1;
    102                 cnt[top]=num+1;
    103             }
    104             else cnt[top]=num;
    105             if(sa[i]<len1)
    106                 ans+=sum;
    107         }
    108 
    109         sum=0,top=0;
    110         for(int i=1;i<n;i++)  //处理B和它前面的A的公共前缀
    111         {
    112             if(height[i]<k)  {top=0;sum=0;continue;}
    113             int num=0;
    114             while(top && sta[top]>height[i])
    115             {
    116                 sum-=(ll)(sta[top]-k+1)*cnt[top];
    117                 sum+=(ll)(height[i]-k+1)*cnt[top];
    118                 num+=cnt[top];
    119                 top--;
    120             }
    121             sta[++top]=height[i];
    122             if(sa[i-1]<len1)
    123             {
    124                 sum+=(ll)height[i]-k+1;
    125                 cnt[top]=num+1;
    126             }
    127             else cnt[top]=num;
    128             if(sa[i]>len1)
    129                 ans+=sum;
    130         }
    131         printf("%I64d
    " , ans);
    132     }
    133     return 0;
    134 }
  • 相关阅读:
    洛谷P5281 [ZJOI2019] Minimax搜索
    势函数
    Comet OJ [Contest #5] 迫真大游戏
    洛谷P3307 [SDOI2013] 项链
    洛谷P5985 [PA2019] Muzyka pop
    CF1205E Expected Value Again
    review
    CF891E Lust
    线性代数
    洛谷P4607 [SDOI2018] 反回文串
  • 原文地址:https://www.cnblogs.com/zyb993963526/p/7623541.html
Copyright © 2020-2023  润新知