【BZOJ 3473】 字符串 (后缀数组+RMQ+二分 | 广义SAM)

3473: 字符串

Description

给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串?

Input

第一行两个整数n,k。
接下来n行每行一个字符串。

Output

一行n个整数,第i个整数表示第i个字符串的答案。

Sample Input

3 1
abc
a
ab

Sample Output

6 1 3

HINT



对于 100% 的数据,1<=n,k<=10^5,所有字符串总长不超过10^5,字符串只包含小写字母。

 

 

 

【分析】

  这道题用后缀数组的被SAM完爆了..貌似...

    首先将所有字符串串在一次做SA,然后我们对于sa上,枚举每个串的每个后缀,求出有几个该后缀的前缀符合条件,那么就要判定区间里面有多少个不同的数,所幸的是这里只需要求是否该数目>=k,所以对于每个位置记录个L(x),表示[L(x),x]中刚好有k个不同的数,且L(x)与x距离最大(参考了CF官方题解)。(这里弄几条链O(n)可以搞出来)

  然后CF上的题解是对于每个后缀二分出长度l,在排好序的后缀上查找最前最后端点使min(这一段的height)>=l。 (这里用到二分+RMQ)

  接下来是去掉一个log的优化:(即1上面的二分长度l改成直接从上一个的位置开始for)

那么我们发现枚举后缀的时候,如果后缀c+S有n个前缀合法(c表示一个字符,s表示一个串),那么对于后缀S,至少有n-1个前缀合法(如果c+S有n个前缀出现不小于k次,那么其子串也是),那么我们就用类似求SA里的height一样的方法,记录一下前面的后缀的合法前缀数,然后这样的总复杂度就成了均摊。

搬运:http://www.cnblogs.com/zyfzyf/p/4149536.html

 

  类似求height的那个优化好厉害!!!

  记得用long long~~

 

代码如下:

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<iostream>
  5 #include<algorithm>
  6 #include<queue>
  7 using namespace std;
  8 #define Maxn 200010
  9 #define LL long long
 10 
 11 int n,k;
 12 char s[Maxn];
 13 int a[Maxn],bl[Maxn],cl;
 14 
 15 int mymin(int x,int y) {return x<y?x:y;}
 16 
 17 void init()
 18 {
 19     scanf("%d%d",&n,&k);
 20     cl=0;
 21     for(int i=1;i<=n;i++)
 22     {
 23         scanf("%s",s+1);
 24         int len=strlen(s+1);
 25         for(int j=1;j<=len;j++) a[++cl]=s[j]-'a'+1,bl[cl]=i;
 26         a[++cl]=28,bl[cl]=-1;
 27     }
 28 }
 29 
 30 int sa[Maxn],y[Maxn],rk[Maxn],Rs[Maxn],wr[Maxn];
 31 void get_sa(int m)
 32 {
 33     memcpy(rk,a,sizeof(rk));
 34     for(int i=0;i<=m;i++) Rs[i]=0;
 35     for(int i=1;i<=cl;i++) Rs[rk[i]]++;
 36     for(int i=1;i<=m;i++) Rs[i]+=Rs[i-1];
 37     for(int i=cl;i>=1;i--) sa[Rs[rk[i]]--]=i;
 38     
 39     int ln=1,p=1;
 40     while(p<cl)
 41     {
 42         int k=0;
 43         for(int i=cl-ln+1;i<=cl;i++) y[++k]=i;
 44         for(int i=1;i<=cl;i++) if(sa[i]>ln) y[++k]=sa[i]-ln;
 45         for(int i=1;i<=cl;i++) wr[i]=rk[y[i]];
 46         
 47         for(int i=0;i<=m;i++) Rs[i]=0;
 48         for(int i=1;i<=cl;i++) Rs[wr[i]]++;
 49         for(int i=1;i<=m;i++) Rs[i]+=Rs[i-1];
 50         for(int i=cl;i>=1;i--) sa[Rs[wr[i]]--]=y[i];
 51         
 52         // memcpy(wr,rk,sizeof(wr));
 53         for(int i=1;i<=cl;i++) wr[i]=rk[i];
 54         for(int i=cl+1;i<=cl+ln;i++) wr[i]=0;
 55         p=1;
 56         rk[sa[1]]=1;
 57         for(int i=2;i<=cl;i++)
 58         {
 59             if(wr[sa[i]]!=wr[sa[i-1]]||wr[sa[i]+ln]!=wr[sa[i-1]+ln]) p++;
 60             rk[sa[i]]=p;
 61         }
 62         
 63         ln*=2;m=p;
 64     }
 65 }
 66 
 67 int height[Maxn];
 68 void get_he()
 69 {
 70     int kk=0;
 71     for(int i=1;i<=cl;i++) if(rk[i]!=1)
 72     {
 73         int j=sa[rk[i]-1];
 74         if(kk) kk--;
 75         while(a[i+kk]==a[j+kk]&&i+kk<=cl&&j+kk<=cl) kk++;
 76         height[rk[i]]=kk;
 77     }
 78 }
 79 
 80 int g[Maxn],lt[Maxn],nt[Maxn],sm[Maxn];
 81 bool p[Maxn];
 82 void get_g()
 83 {
 84     memset(lt,0,sizeof(lt));
 85     for(int i=1;i<=cl;i++)
 86     {
 87         sm[i]=lt[bl[sa[i]]];
 88         lt[bl[sa[i]]]=i;
 89     }
 90     memset(lt,0,sizeof(lt));
 91     memset(p,0,sizeof(p));
 92     int h=0,now;
 93     for(int i=1;i<=cl;i++)
 94     {
 95         if(!p[bl[sa[i]]]) h++;
 96         p[bl[sa[i]]]=1;
 97         if(h==k) {now=i;break;}
 98     }int id=0;
 99     for(int i=now;i>=1;i--)
100     {
101         if(p[bl[sa[i]]])
102         {
103             h--;
104             if(id)
105             {
106                 nt[i]=id;lt[id]=i;lt[i]=0;
107             }
108             id=i;
109             p[bl[sa[i]]]=0;
110         }
111         if(h==0) {g[now]=i;break;}
112     }
113     for(int i=now+1;i<=cl;i++)
114     {
115         if(sm[i]>g[i-1])
116         {
117             g[i]=g[i-1];
118             nt[now]=i;lt[i]=now;
119             nt[lt[sm[i]]]=nt[sm[i]];
120             lt[nt[sm[i]]]=lt[sm[i]];
121         }
122         else
123         {
124             nt[now]=i;lt[i]=now;
125             g[i]=nt[g[i-1]];
126         }
127         now=i;
128     }
129 }
130 
131 int d[Maxn][20];
132 void init_rmq()
133 {
134     for(int i=2;i<=cl;i++) d[i][0]=height[i];
135     for(int j=1;(1<<j)<=cl;j++)
136      for(int i=2;i+(1<<j)-1<=cl;i++)
137       d[i][j]=mymin(d[i][j-1],d[i+(1<<j-1)][j-1]);    
138 }
139 
140 int rmq(int l,int r)
141 {
142     int i=0;
143     while(l+(1<<i)-1<=r) i++;
144     return mymin(d[l][i-1],d[r-(1<<i-1)+1][i-1]);
145 }
146 
147 int t_div(int l,int r,int x,int kk)
148 {
149     if(l>r) return x;
150     while(l<r)
151     {
152         int mid;
153         if(r<x)
154         {
155             mid=(l+r)>>1;
156             if(rmq(mid+1,x)>=kk) r=mid;
157             else l=mid+1;
158         }
159         else
160         {
161             mid=(l+r+1)>>1;
162             if(rmq(x+1,mid)>=kk) l=mid;
163             else r=mid-1;
164         }
165     }
166     if(l<x&&rmq(l+1,x)>=kk) return l;
167     if(l>x&&rmq(x+1,l)>=kk) return l;
168     return x;
169 }
170 
171 LL ans[Maxn];
172 void ffind()
173 {
174     memset(ans,0,sizeof(ans));
175     int mx=0;
176     for(int i=1;i<=cl;i++) if(bl[i]!=-1)
177     {
178         int kk=1;
179         if(mx>=i) kk=mx-i+2;
180         while(1)
181         {
182             int l=t_div(1,rk[i]-1,rk[i],kk),r=t_div(rk[i]+1,cl,rk[i],kk);
183             if(g[r]<l||bl[i+kk-1]!=bl[i]||i+kk-1>cl) {kk--;break;}
184             kk++;
185         }
186         ans[bl[i]]+=kk;
187         mx=i+kk-1;
188     }
189 }
190 
191 int main()
192 {
193     init();
194     get_sa(30);
195     get_he();
196     get_g();
197     init_rmq();
198     ffind();
199     for(int i=1;i<=n;i++) 
200     {
201         if(i!=1) printf(" ");
202         printf("%lld",ans[i]);
203     }
204     return 0;
205 }
[BZOJ2473]

 

2016-08-25 12:00:01

 


 

 

  233学了广义SAM的我回来更新这题了~~

  其实我还不是很懂。。

  看GDXB的题解:

每个节点维护一个set,存它是哪个串的,然后整理一下parent tree从下往上合并set(他们说是启发式合并,然而我用了最普通的暴力合并)。
最后每一个串在自动机上跑一遍,如果set的size<k就不断跳pre,保证当前的这一条后缀的前缀都是可行的,每个可行点的贡献是step[i]。

 

  广义SAM不能用step或者son求拓扑序,这个的pre更新用dfs即可。 (蒟蒻表示又被坑了!)

  统计答案的时候可以用step,大概是机上有原串吧~

 

代码如下:

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<iostream>
  5 #include<algorithm>
  6 #include<queue>
  7 #include<cmath>
  8 #include<set>
  9 using namespace std;
 10 #define Maxn 100010
 11 #define LL long long
 12 
 13 char s[Maxn],ss[Maxn];
 14 int l,bg[Maxn];
 15 
 16 struct node
 17 {
 18     int pre,son[30],step;
 19 }t[2*Maxn];int tot=0;
 20 int last;
 21 
 22 set<int > d[2*Maxn];
 23 
 24 void upd(int x)
 25 {
 26     memset(t[x].son,0,sizeof(t[x].son));
 27     d[x].clear();
 28     t[x].pre=0;
 29 }
 30 
 31 void get_d(int x,int y)
 32 {
 33     if(x==0) return;
 34     set<int>:: iterator st,ed;
 35     st=d[y].begin();
 36     ed=d[y].end();
 37     while(st!=ed)
 38     {
 39         d[x].insert(*st);
 40         st++;
 41     }
 42 }
 43 
 44 void extend(int x,int k)
 45 {
 46     int np=++tot;
 47     upd(np);d[np].insert(x);
 48     t[np].step=t[last].step+1;
 49     int now=last;
 50     while(now&&!t[now].son[k])
 51     {
 52         t[now].son[k]=np;
 53         now=t[now].pre;
 54     }
 55     if(!now) t[np].pre=1;
 56     else
 57     {
 58         int p=now,q=t[now].son[k];
 59         if(t[p].step+1==t[q].step) t[np].pre=q;
 60         else
 61         {
 62             int nq=++tot;
 63             upd(nq);
 64             memcpy(t[nq].son,t[q].son,sizeof(t[nq].son));
 65             get_d(nq,q);
 66             t[nq].pre=t[q].pre;
 67             t[q].pre=t[np].pre=nq;
 68             t[nq].step=t[p].step+1;
 69             while(now&&t[now].son[k]==q)
 70             {
 71                 t[now].son[k]=nq;
 72                 now=t[now].pre;
 73             }
 74         }
 75     }
 76     last=np;
 77 }
 78 
 79 int n,kk;
 80 void init()
 81 {
 82     scanf("%d%d",&n,&kk);
 83     t[++tot].step=0;
 84     upd(tot);
 85     int sl=0;
 86     for(int i=1;i<=n;i++)
 87     {
 88         scanf("%s",ss+1);
 89         l=strlen(ss+1);
 90         last=1;
 91         bg[i]=sl+1;
 92         for(int j=1;j<=l;j++)
 93         {
 94             int k=ss[j]-'a'+1;
 95             extend(i,k);
 96             s[++sl]=ss[j];
 97         }
 98     }
 99     bg[n+1]=sl+1;
100 }
101 
102 struct hp
103 {
104     int x,y,next;
105 }a[2*Maxn];int al=0;
106 int first[2*Maxn];
107 
108 void ins(int x,int y)
109 {
110     a[++al].x=x;a[al].y=y;
111     a[al].next=first[x];first[x]=al;
112 }
113 
114 void dfs(int x)
115 {
116     for(int i=first[x];i;i=a[i].next)
117     {
118         dfs(a[i].y);
119         get_d(x,a[i].y);
120     }
121 }
122 
123 int main()
124 {
125     init();
126     memset(first,0,sizeof(first));
127     for(int i=1;i<=tot;i++) ins(t[i].pre,i);
128     dfs(1);
129     bool ok=1;
130     for(int i=1;i<=n;i++)
131     {
132         int now=1;
133         LL ans=0;
134         for(int j=bg[i];j<bg[i+1];j++)
135         {
136             int k=s[j]-'a'+1;
137             now=t[now].son[k];
138             while(now&&d[now].size()<kk) now=t[now].pre;
139             if(!now) now=1;
140             ans+=t[now].step;
141         }
142         if(!ok) printf(" ");
143         else ok=0;
144         printf("%lld",ans);
145     }
146     return 0;
147 }
[BZOJ 3473]

 

2016-09-20 21:28:28

 

转载于:https://www.cnblogs.com/Konjakmoyu/p/5806234.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值