「专题总结」后缀数组1~2

%%%mikufun他太巨了

你们快去%他啊

SA?我不会啊

 

这个专题其实有两道题是好久以前做的了,当时的理解非常不深刻,做题也就是各种扔结论。

而被叫去讲了一节课,这回大约是理解一些了。

 

Sandy的卡片

$Description:$

Sandy和Sue的热衷于收集干脆面中的卡片。然而,Sue收集卡片是因为卡片上漂亮的人物形象,而Sandy则是为了积攒卡片兑换超炫的人物模型。每一张卡片都由一些数字进行标记,第i张卡片的序列长度为Mi,要想兑换人物模型,首先必须要集够N张卡片,对于这N张卡片,如果他们都有一个相同的子串长度为k,则可以兑换一个等级为k的人物模型。相同的定义为:两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。Sandy的卡片数远远小于要求的N,于是Sue决定在Sandy的生日将自己的卡片送给Sandy,在Sue的帮助下,Sandy终于集够了N张卡片,但是,Sandy并不清楚他可以兑换到哪个等级的人物模型,现在,请你帮助Sandy和Sue,看看他们最高能够得到哪个等级的人物模型。N<=1000,M<=100

题意明显提示差分。之后问题转化为多串匹配最大长度。

套路:SA处理多串问题就把它们都接起来。

二分答案,$O(NM)check$在连成片的height上查sa,看看是否每个串都出现过。

 1 #include
 2 #define S 180005
 3 int x[S],y[S],c[S],sa[S],s[S],R[1111],rk[S],height[S],cnt,n,al[1111],alc,bl[S],st[S],top,mx=1900,ans,ml,mr=100;
 4 void Suffix_Array(int*a,int n,int m){
 5     for(int i=1;i<=m;++i)c[i]=0;
 6     for(int i=1;i<=n;++i)x[i]=s[i]+1900;
 7     for(int i=1;i<=n;++i)c[x[i]]++;
 8     for(int i=1;i<=m;++i)c[i]+=c[i-1];
 9     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
10     for(int len=1;n!=m;len<<=1){
11         int num=0;
12         for(int i=n-len+1;i<=n;++i)y[++num]=i;
13         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
14         for(int i=1;i<=m;++i)c[i]=0;
15         for(int i=1;i<=n;++i)c[x[i]]++;
16         for(int i=1;i<=m;++i)c[i]+=c[i-1];
17         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
18         for(int i=1;i<=n;++i)y[i]=x[i],x[i]=0;
19         x[sa[1]]=m=1;
20         for(int i=2;i<=n;++i)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len])?m:++m;
21     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
22     for(int i=1,k=0;i<=n;height[rk[i++]]=k,k=k?k-1:k)while(a[i+k]==a[sa[rk[i]-1]+k])k++;
23 }
24 void clear(){while(top)al[st[top--]]=0;alc=0;}
25 bool chk(int len){
26     for(int i=1;i<=cnt;++i){
27         if(height[i]<len)clear();
28         if(!al[bl[sa[i]]]&&sa[i]+len-1<=R[bl[sa[i]]]){
29             al[bl[sa[i]]]=1;alc++;st[++top]=bl[sa[i]];
30             if(alc==n)return 1;
31         }
32     }return clear(),0;
33 }
34 void pt(int *a,int n=cnt){for(int i=1;i<=n;++i)printf("%d ",a[i]);puts("");}
35 int main(){
36     scanf("%d",&n);al[0]=1;
37     for(int i=1,x,l,L;i<=n;++i){
38         scanf("%d%d",&L,&l);
39         for(int t=2;t<=L;++t)scanf("%d",&x),s[++cnt]=x-l,bl[cnt]=i,l=x;
40         s[++cnt]=++mx;R[i]=cnt-1;
41     }Suffix_Array(s,cnt,4888);//pt(s);pt(rk);pt(height);
42     while(ml<=mr)if(chk(ml+mr>>1))ans=ml=ml+mr>>1,ml++;else mr=(ml+mr>>1)-1;
43     printf("%d\n",ans+1);
44 }
View Code

 

喵星球上的点名:

$Description:$

a180285幸运地被选做了地球到喵星球的留学生。他发现喵星人在上课前的点名现象非常有趣。 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成。喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。 然而,由于喵星人的字码过于古怪,以至于不能用ASCII码来表示。为了方便描述,a180285决定用数串来表示喵星人的名字。
现在你能帮助a180285统计每次点名的时候有多少喵星人答到,以及M次点名结束后每个喵星人答到多少次吗?

1<=N<=20000,1<=M<=50000,喵星人的名字总长和点名串的总长分别不超过100000,保证喵星人的字符串中作为字符存在的数不超过10000。

好题。

继续套路把所有串接起来,然后呢?

预处理,二分答案得到每个询问对应的匹配的rank区间。

多次询问不强制在线,考虑离线算法。

可以采取莫队,通过移动端点加入或删除元素,去重问题就是打标记表示出现了几次。

对于第二问,就是数颜色。

对于第一问,考虑某一种颜色出现持续了几次询问,每次彻底删除时累加答案。

 1 #include
 2 using namespace std;
 3 #define S 423456
 4 int a[S],bl[S],rk[S],sa[S],h[S],buc[S],x[S],y[S],n,m,c,M=10000,L[S],R[S];
 5 int ST[20][S],hb[S],ans,T,tms[S],apt[S],tot[S],Ans[S];
 6 struct Q{
 7     int l,r,o;
 8     friend bool operator<(Q q,Q x){return q.l/500!=x.l/500?q.l/500500:q.r<x.r;}
 9 }q[100005];
10 void Suffix_Array(int*a,int*c,int*x,int*y,int n,int m=M){
11     for(int i=1;i<=m;++i)c[i]=0;
12     for(int i=1;i<=n;++i)c[x[i]=a[i]]++;
13     for(int i=1;i<=m;++i)c[i]+=c[i-1];
14     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
15     for(int len=1,num;num=0,n^m;len<<=1){
16         for(int i=n-len+1;i<=n;++i)y[++num]=i;
17         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
18         for(int i=1;i<=m;++i)c[i]=0;
19         for(int i=1;i<=n;++i)c[x[i]]++;
20         for(int i=1;i<=m;++i)c[i]+=c[i-1];
21         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
22         swap(x,y);x[sa[1]]=m=1;
23         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
24     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
25     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(a[i+k]==a[sa[rk[i]-1]+k])k++;
26 }
27 void build_ST(){
28     for(int i=1;i<=c;++i)ST[0][i]=h[i];
29     for(int t=1;t<=19;++t)for(int i=1;i+(1<1<=c;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<1)]);
30 }
31 void find(int p,int H,int&l,int&r){
32     l=p+1;r=p;//printf("f:%d %d\n",p,ST[1][12]);
33     for(int i=19;~i;--i){
34         if(l>1<1<=H)l-=1<<i;
35         if(r+(1<1]>=H)r+=1<<i;
36     }l--;
37 }
38 void del(int p){
39     int co=bl[sa[p]];
40     if(tms[co]==1)tot[co]+=T-apt[co],ans--;
41     tms[co]--;
42 }
43 void add(int p){
44     int co=bl[sa[p]];
45     if(!tms[co])apt[co]=T,ans++;
46     tms[co]++;
47 }
48 int main(){
49     scanf("%d%d",&n,&m);
50     for(int i=1,l;i<=n;++i){
51         scanf("%d",&l);
52         for(int t=1;t<=l;++t)scanf("%d",&a[++c]),bl[c]=i;
53         a[++c]=++M;
54         scanf("%d",&l);
55         for(int t=1;t<=l;++t)scanf("%d",&a[++c]),bl[c]=i;
56         a[++c]=++M;
57     }
58     for(int i=1,l;i<=m;++i){
59         scanf("%d",&l);L[i]=c+1;
60         for(int t=1;t<=l;++t)scanf("%d",&a[++c]);
61         R[i]=c;a[++c]=++M;
62     }
63     Suffix_Array(a,buc,x,y,c);build_ST();
64 //    for(int i=1;i<=c;++i)printf("%d ",a[i]);puts("");
65 //    for(int i=1;i<=c;++i)printf("%d ",rk[i]);puts("");
66 //    for(int i=1;i<=c;++i)printf("%d ",h[i]);puts("");
67     for(int i=1;i<=m;++i)find(rk[L[i]],R[i]-L[i]+1,q[i].l,q[i].r),q[i].o=i;
68     sort(q+1,q+1+m);
69     int l=1,r=0;
70     for(T=1;T<=m;++T){
71         while(l);
72         while(r>q[T].r)del(r--);
73         while(l>q[T].l)add(--l);
74         while(rr);
75         Ans[q[T].o]=ans;//printf("%d %d\n",l,r);
76     }for(int i=1;i<=m;++i)printf("%d\n",Ans[i]-1);
77     while(l<=r)del(l++);
78     for(int i=1;i<=n;++i)printf("%d ",tot[i]);puts("");
79 }
View Code

 

字符串:

$Description:$

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CEO,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?1<=n,m<=100,000

好题。

看清题,是子串和串,而不是子串和子串。

首先把子串转化成后缀肯定没问题,然后就是查一片后缀与一个后缀的最优lcp。

lcp最大的一定是rank最相近的,查区间前驱后继。

但是还没完,子串和后缀毕竟不一样,可能匹配的多但是你长度不够,这多了一种限制。

于是二分答案,长度不够的直接舍弃从而缩小区间,再查区间前驱后继就可以了。

 1 #include
 2 using namespace std;
 3 #define S 280005
 4 #define md (cl+cr>>1)
 5 int sa[S],h[S],rk[S],c[S],x[S],y[S],n,m,ST[18][S],hb[S];char s[S];
 6 void Suffix_Array(char*a,int*x,int*y,int n,int m){
 7     for(int i=1;i<=m;++i)c[i]=0;
 8     for(int i=1;i<=n;++i)c[x[i]=a[i]-'a'+1]++;
 9     for(int i=1;i<=m;++i)c[i]+=c[i-1];
10     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
11     for(int len=1,num;num=0,n^m;len<<=1){
12         for(int i=n-len+1;i<=n;++i)y[++num]=i;
13         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
14         for(int i=1;i<=m;++i)c[i]=0;
15         for(int i=1;i<=n;++i)c[x[i]]++;
16         for(int i=1;i<=m;++i)c[i]+=c[i-1];
17         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
18         swap(x,y);x[sa[1]]=m=1;
19         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
20     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
21     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(a[i+k]==a[sa[rk[i]-1]+k])k++;
22 }
23 void build_ST(){
24     for(int i=1;i<=n;++i)ST[0][i]=h[i];
25     for(int i=2;i<=n;++i) hb[i]=hb[i>>1]+1;
26     for(int t=1;t<=17;++t)for(int i=1;i+(1<1)<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<1)]);
27 }
28 int query(int l,int r){int B=hb[r-l+1];return l>r?n:min(ST[B][l],ST[B][r-(1<1]);}
29 int pc,rt[S],w[S<<7],lc[S<<7],rc[S<<7];
30 void insert(int&p,int cp,int v,int cl=1,int cr=n){
31     p=++pc;w[p]=w[cp]+1;
32     if(cl==cr)return;
33     if(v<=md)insert(lc[p],lc[cp],v,cl,md),rc[p]=rc[cp];
34     else insert(rc[p],rc[cp],v,md+1,cr),lc[p]=lc[cp];
35 }
36 int value(int pl,int pr,int R,int cl=1,int cr=n){
37     if(cl==cr)return R==0&&w[pr]-w[pl]?cl:0;
38     if(w[lc[pr]]-w[lc[pl]]<=R)return value(rc[pl],rc[pr],R-(w[lc[pr]]-w[lc[pl]]),md+1,cr);
39     return value(lc[pl],lc[pr],R,cl,md);
40 }
41 int Rank(int pl,int pr,int V,int cl=1,int cr=n){
42     if(cl==cr)return 0;
43     if(V>md)return w[lc[pr]]-w[lc[pl]]+Rank(rc[pl],rc[pr],V,md+1,cr);
44     return Rank(lc[pl],lc[pr],V,cl,md);
45 }
46 int chk(int A,int B,int rk,int X){B-=X-1;
47     int x=rt[A-1],y=rt[B],pre=value(x,y,Rank(x,y,rk+1)-1),suc=value(x,y,Rank(x,y,rk));
48     if(pre&&query(pre+1,rk)>=X)return 1;
49     if(suc&&query(rk+1,suc)>=X)return 1;
50     return 0;
51 }
52 int main(){//freopen("1.in","r",stdin);//freopen("1.out","w",stdout);
53     scanf("%d%d%s",&n,&m,s+1);Suffix_Array(s,x,y,n,26);build_ST();
54     for(int i=1;i<=n;++i)insert(rt[i],rt[i-1],rk[i]);
55     for(int i=1,a,b,c,d;i<=m;++i){
56         scanf("%d%d%d%d",&a,&b,&c,&d);
57         int l=0,r=min(b-a,d-c)+1,ans;
58         while(l<=r)if(chk(a,b,rk[c],l+r>>1))ans=l=l+r>>1,l++;else r=(l+r>>1)-1;
59         printf("%d\n",ans);
60     }
61 }
View Code

 

差异:

$Description:$

一个长度为n的字符串S,令$T_i$表示它从第i个字符开始的后缀。求所有串两两之间非lcp部分的和

2<=N<=500000

单调栈得到height最小值的控制区间。

 1 #include
 2 #include
 3 #define int long long
 4 char s[500005];
 5 int x[500005],y[500005],sa[500005],rk[500005],l=1,c[500005],m=27,h[500005];
 6 int sta[500005],sta2[500005],top,ans,tr[500005],tl[500005];
 7 main(){
 8     scanf("%s",s);
 9     while(s[l]) l++;//统计长度。在末尾加上了一个空字符
10     for(int i=0;i<=l;++i) c[s[i]?x[i]=s[i]-'a'+1:0]++;//统计各字符出现次数,顺便把字符串s转化为数串x
11     for(int i=0;i<=m;++i) c[i]+=c[i-1];//累加得到出现次数的前缀和,相当与划分出了多个区间
12     for(int i=l;~i;--i) sa[--c[x[i]]]=i;//初步确定每个范围里有哪些串,但内部排名未知
13     for(int len=1,p=0;len<=l+1;len<<=1,p=0){//枚举前后两截(二元组)的长度,倍增求解每个串在长度为len<<1的所有串中的排名
14         for(int i=l;i>=l-len+1;--i) y[p++]=i;//没有后半截的直接记录,y数组存的是前半截(也就是整个串)的起始位置
15         for(int i=0;i<=l;++i) if(sa[i]>=len) y[p++]=sa[i]-len;//有后半截的,那就记录下它对应的左半截
16         for(int i=0;i<=m;++i) c[i]=0;//清空字符出现次数
17         for(int i=0;i<=l;++i) c[x[i]]++;//重新累加每个长度为len的字符串的出现次数
18         for(int i=1;i<=m;++i) c[i]+=c[i-1];//还是前缀和,完全和循环外面的一样,划分出大致区间
19         for(int i=l;~i;--i) sa[--c[x[y[i]]]]=y[i];//对于每个串的前半截讨论其排名
20         p=0;std::swap(x,y); x[sa[0]]=0;//清空p,xy数组滚动交换,确定排名第0的串是一个空串
21         for(int i=1;i<=l;++i) x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+len]==y[sa[i-1]+len]?p:++p;//类似离散化,求出已经区分开的串的种数,并更新字符串和字典排名
22         if(p==l)break;else m=p;//如果已经全部区分开了,可以跳出循环,否则更新串种数然后继续区分
23     }
24     for(int i=0;i<=l;++i) rk[sa[i]]=i;//求出sa的逆数组,表示排名为i的串的开始位置
25     for(int i=0,k=0;i<=l;h[rk[i++]]=k,k=k?k-1:0) while(s[i+k]==s[sa[rk[i]-1]+k]) k++;//求height,s[i+k]可以写作s[sa[rk[i]]+k]更好理解
26     //性质:排名为i,j(i27     //转化:已知一段序列,求所有字串的最小值的和
28     sta[0]=-3;h[l+1]=-2;h[1]=-1;
29     for(int i=2;i<=l+1;++i){
30         while(sta[top]>=h[i])tr[sta2[top--]]=i-1;
31         sta[++top]=h[i];sta2[top]=i;
32     }
33     for(int i=l;i;--i){
34         while(sta[top]>h[i])tl[sta2[top--]]=i+1;
35         sta[++top]=h[i];sta2[top]=i;
36     }
37     for(int i=2;i<=l;++i)ans+=h[i]*(tr[i]-i+1)*(i-tl[i]+1);
38     printf("%lld\n",((l-1)*l*(l+1)>>1)-(ans<<1));
39 }
View Code

 

相似子串:

$Description:$

所有子串排序后,多次询问查询排名为i,j的串的$lcp^2+lcs^2$

N≤100000,Q≤100000

关键在于查询排名为i的子串是哪一个。处理出所有本质不同的子串。

$sum_i=\sum\limits_{j=1}^{i} n+1-sa[i]-height[i]$

后面部分是一个后缀贡献的新子串数。。根据sum二分得到子串到底是哪一个后缀的前缀。

然后lcp和lcs都用ST弄出来对长度取min。就可以回答了。

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 using namespace std;
 7 #define int long long
 8 char s[100005];
 9 int h[100005],rk[100005],sa[100005],x[100005],y[100005],c[100005],m,l,lbt[400005];
10 int h2[100005],tot[100005],st1[20][400005],st2[20][400005],siz=1,sa2[100005];
11 long long fab(long long a){return a*a;}
12 void build(){
13     for(int i=1;i<=l;++i)st1[0][i]=h[i],st2[0][i]=h2[i];
14     for(int i=1;i<=19;++i)for(int j=1;j<=l;++j)
15         st1[i][j]=min(st1[i-1][j],st1[i-1][j+(1<1)]),
16         st2[i][j]=min(st2[i-1][j],st2[i-1][j+(1<1)]);
17 }
18 int ask1(int ll,int rr){
19     int le=rr-ll+1;while(le!=(le&-le))le-=le&-le;
20     return min(st1[lbt[le]][ll],st1[lbt[le]][rr-le+1]);
21 }
22 int ask2(int ll,int rr){
23     int le=rr-ll+1;while(le!=(le&-le))le-=le&-le;
24     return min(st2[lbt[le]][ll],st2[lbt[le]][rr-le+1]);
25 }
26 void get(){m=27;
27     for(int i=0;i<=m;++i)c[i]=0;c[0]++;
28     for(int i=0;i'a'+1]++;
29     for(int i=1;i<=m;++i)c[i]+=c[i-1];
30     for(int i=0;i<=l;++i)sa[--c[x[i]]]=i;//初级排序
31     for(int len=1,p=0;len<=l+1;len<<=1,p=0){
32         for(int i=l;i>=l-len+1;--i)y[p++]=i;
33         for(int i=0;i<=l;++i)if(sa[i]>=len)y[p++]=sa[i]-len;//第二关键字排序
34         for(int i=0;i<=m;++i)c[i]=0;
35         for(int i=0;i<=l;++i)c[x[i]]++;
36         for(int i=1;i<=m;++i)c[i]+=c[i-1];
37         for(int i=l;i>=0;--i)sa[--c[x[y[i]]]]=y[i];//双关键字桶排序,x优先
38         p=0; swap(x,y); x[sa[0]]=0;
39         for(int i=1;i<=l;++i) x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+len]==y[sa[i-1]+len]?p:++p;//离散化新子串
40         if(p==l)break;else m=p;
41     }
42     for(int i=0;i<=l;++i)rk[sa[i]]=i;
43     for(int i=0,k=0;i<=l;h[rk[i++]]=k,k=k?k-1:0)while(s[i+k]==s[sa[rk[i]-1]+k])k++;
44 }
45 int find(int p){
46     int le=1,r=l;
47     while(le1){
48         if(tot[le+r>>1]>=p)r=le+r>>1;
49         else le=(le+r>>1)+1;
50     }
51     if(tot[le]>=p)return le;return r;
52 }
53 main(){
54     int q;scanf("%lld%lld",&l,&q);
55     scanf("%s",s);get();for(int i=0;i<=19;++i)lbt[1<i;
56     for(int i=1;i<=l;++i)tot[i]=tot[i-1]+l-sa[i]-h[i];//,printf("%lld\n",tot[i]);
57     reverse(s,s+l);swap(h,h2);swap(sa,sa2);get();swap(h,h2);build();
58     for(int i=1,a,b,aa,bb,ax,bx,ll;i<=q;++i){
59         scanf("%lld%lld",&a,&b);if(a>tot[l]||b>tot[l]){puts("-1");continue;}
60         aa=find(a);bb=find(b);ax=rk[tot[aa]-a];bx=rk[tot[bb]-b];//printf("%lld %lld %lld %lld\n",aa,bb,ax,bx);
61         if(ax>bx)ax^=bx,bx^=ax,ax^=bx;
62         if(aa>bb)aa^=bb,bb^=aa,aa^=bb;
63         ll=min(l-sa2[aa]-tot[aa]+a,l-sa2[bb]+b-tot[bb]);//printf("%lld\n",ll);
64         printf("%lld\n",fab(aa==bb?ll:min(ask1(aa+1,bb),ll))+fab(ax==bx?ll:min(ask2(ax+1,bx),ll)));
65     }
66 }
View Code

 

品酒大会:

$Description:$

一年一度的「幻影阁夏日品酒大会」隆重开幕了。

大会包含品尝和趣味挑战两个环节,分别向优胜者颁发「首席品酒家」和「首席猎手」两个奖项,吸引了众多品酒师参加。

在大会的晚餐上,调酒师 Rainbow 调制了n杯鸡尾酒。鸡尾酒排成一行,其中第i杯酒被贴上了一个标签$s_i$,每个标签都是小写英文字母。

设str(l,r)表示第l杯酒到第r杯酒的标签顺次连接构成的字符串。若str(p,p0)=str(q,q0) (q0-q=p0-p=k)则称第p杯酒与第q杯酒是「k相似」的。

当然两杯「k相似」的酒同时也是「k-1相似」...「0相似」的。特别地,对于任意第i杯酒和第j杯酒都是「0相似」的。

在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了「首席品酒家」的称号,其中第i杯酒美味度为$w_i$

现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第i杯酒与第j杯酒调兑在一起,将得到一杯美味度为$w_i \times w_j$的酒。

现在请各位品酒师分别对于所有k,统计出有多少种方法可以选出两杯「 相似」的酒,并回答选择两杯「 相似」的酒调兑可以得到的美味度的最大值。

$n \le 3 \times 10^5,|a_i| \le 10^9$

题不错。

倒序考虑,答案满足可加性。

不断提高height数组的门槛,合并两段贡献答案。

用set维护可能会T,set只需要维护极端的4个元素即可。

 1 #include
 2 using namespace std;
 3 #define S 300005
 4 vector<int>v[S];
 5 multiset<int>s[S];
 6 int sa[S],rk[S],h[S],n,w[S],f[S],R,c[S],x[S],y[S],sz[S];long long ans[2][S];char a[S];
 7 void Suffix_Array(char*s,int n,int m){
 8     for(int i=1;i<=n;++i)c[x[i]=s[i]-'a'+1]++;
 9     for(int i=1;i<=m;++i)c[i]+=c[i-1];
10     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
11     for(int len=1,num;num=0,n!=m;len<<=1){
12         for(int i=n-len+1;i<=n;++i)y[++num]=i;
13         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
14         for(int i=1;i<=m;++i)c[i]=0;
15         for(int i=1;i<=n;++i)c[x[i]]++;
16         for(int i=1;i<=m;++i)c[i]+=c[i-1];
17         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
18         for(int i=1;i<=n;++i)y[i]=x[i],x[i]=0;
19         x[sa[1]]=m=1;
20         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
21     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
22     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(s[i+k]==s[sa[rk[i]-1]+k])k++;
23 }
24 int find(int p){return p==f[p]?p:f[p]=find(f[p]);}
25 void merge(int a,int b){
26     f[b]=a;
27     ans[0][R]+=1ll*sz[a]*sz[b];sz[a]+=sz[b];
28     for(auto I:s[b])s[a].insert(I);
29     auto f=s[a].begin(),l=(--s[a].end());
30     ans[1][R]=max(ans[1][R],max(1ll*(*f)*(*(++f)),1ll*(*l)*(*(--l))));
31     if(s[a].size()>4){
32         auto A=s[a].begin(),B=--s[a].end();int x=*A,y=*(++A),z=*B,k=*(--B);
33         s[a].clear();s[a].insert(x);s[a].insert(y);s[a].insert(z);s[a].insert(k);
34     }
35 }
36 int main(){//freopen("1.in","r",stdin);freopen("wa.out","w",stdout);
37     scanf("%d%s",&n,a+1);Suffix_Array(a,n,27);ans[1][n]=-1e18;
38     for(int i=1,w;i<=n;++i)scanf("%d",&w),s[rk[i]].insert(w),f[i]=i,sz[i]=1;
39     for(int i=2;i<=n;++i)v[h[i]].push_back(i);
40     for(R=n-1;ans[0][R]=ans[0][R+1],ans[1][R]=ans[1][R+1],~R;--R)for(auto I:v[R])merge(find(I-1),I);
41     for(int i=0;i"%lld %lld\n",ans[0][i],ans[1][i]*(ans[0][i]?1:0));
42 }
View Code

 

外星联络:

$Description:$

小 P 在看过电影《超时空接触》(Contact)之后被深深的打动,决心致力于寻找外星人的事业。于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息。虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号改写为由 0 和 1 构成的串, 并坚信外星人的信息就隐藏在其中。他认为,外星人发来的信息一定会在他接受到的 01 串中重复出现,所以他希望找到他接受到的 01 串中所有重复出现次数大于 1 的子串。但是他收到的信号串实在是太长了,于是,他希望你能编一个程序来帮助他。0 <= N <=3000

$O(n^2)$怎么做都行了。考虑height数组和所有子串排序的样子即可。

 1 #include
 2 using namespace std;
 3 #define S 3333
 4 int n,sa[S],rk[S],x[S],y[S],h[S],c[S],C[S],ans[S*S],sum[S],t;char s[S];
 5 void Suffix_Array(char*s,int*x,int*y,int n,int m){
 6     for(int i=1;i<=n;++i)c[x[i]=s[i]-47]++;
 7     for(int i=1;i<=m;++i)c[i]+=c[i-1];
 8     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
 9     for(int len=1,num;num=0,n^m;len<<=1){
10         for(int i=n-len+1;i<=n;++i)y[++num]=i;
11         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
12         for(int i=1;i<=m;++i)c[i]=0;
13         for(int i=1;i<=n;++i)c[x[i]]++;
14         for(int i=1;i<=m;++i)c[i]+=c[i-1];
15         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
16         swap(x,y);x[sa[1]]=m=1;
17         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
18     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
19     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(s[i+k]==s[sa[rk[i]-1]+k])k++;
20 }
21 int main(){
22     scanf("%d%s",&n,s+1);Suffix_Array(s,x,y,n,2);
23     for(int i=n;i;--i){
24         sum[n-sa[i]+1]++;
25         for(int j=n-sa[i]+1;j>h[i];--j){if(sum[j]>1)ans[++t]=sum[j];sum[j-1]+=sum[j],sum[j]=0;}
26     }while(t)printf("%d\n",ans[t--]);
27 }
View Code

 

跳蚤:

$Description:$

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个,并在选出来的 k 个子串中选择字典序最大的那一个。他称其为“魔力串”。现在他想找一个最优的分法让魔力串字典序最小。

$n \le 10^5$

颓的题解。挺难的。

最大的最小——二分答案。

肯定不是二分串,取而代之,二分子串排名,和相似子串一样求出sum。

然后贪心,倒序考虑从当前串的头部加入新字符。

如果字典序超过了就砍断。

为什么要倒序?因为每次加入一个字符产生的子串有前缀关系,最长的那一个字典序最大,就不用查询更短的子串了。

 1 #include
 2 using namespace std;
 3 #define S 100005
 4 #define int long long
 5 char s[S],r[S],R[S];int x[S],y[S],sa[S],rk[S],c[S],h[S],sum[S],n=1,k,ST[20][S],hb[S];
 6 void Suffix_Array(char*s,int*x,int*y,int n,int m){
 7     for(int i=1;i<=n;++i)c[x[i]=s[i]-'a'+1]++;
 8     for(int i=1;i<=m;++i)c[i]+=c[i-1];
 9     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
10     for(int len=1,num;num=0,n^m;len<<=1){
11         for(int i=n-len+1;i<=n;++i)y[++num]=i;
12         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
13         for(int i=1;i<=m;++i)c[i]=0;
14         for(int i=1;i<=n;++i)c[x[i]]++;
15         for(int i=1;i<=m;++i)c[i]+=c[i-1];
16         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
17         swap(x,y);x[sa[1]]=m=1;
18         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
19     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
20     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(s[i+k]==s[sa[rk[i]-1]+k])k++;
21 }
22 void build_ST(){
23     for(int i=2;i<=n;++i)ST[0][i]=h[i],hb[i]=hb[i>>1]+1;
24     for(int t=1;t<=18;++t)for(int i=1;i+(1<1<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<1)]);
25 }
26 int ask(int l,int r){int B=hb[r-l+1];return min(ST[B][l],ST[B][r-(1<1]);}
27 int lcp(int a,int b){if(rk[a]>rk[b])a^=b^=a^=b;return a==b?n:ask(rk[a]+1,rk[b]);}
28 bool chk(int Rk){
29     int cut=1,lst=n,I=1,len;
30     for(int i=1;i<=n;++i)r[i]=s[i],R[i]=0;
31     for(;sum[I]1-sum[I]+Rk;
32     for(int i=1;i<=len;++i)R[i]=s[sa[I]+i-1];//printf("check:%s\n",R+1);
33     for(int i=n;i;--i){
34         int L=min(lcp(i,sa[I]),min(lst-i+1,len));//printf("%d %d\n",i,L);
35         if(r[i+L]>R[L+1]){for(int j=i+1;j<=lst;++j)r[j]=0;lst=i,cut++;}//printf("cut at %d\n",i);}
36     }//printf("%d %d %d %d\n",Rk,sa[I],len,cut);
37     return cut<=k;
38 }
39 main(){
40     scanf("%lld%s",&k,s+1);while(s[n])n++;n--;Suffix_Array(s,x,y,n,26);build_ST();
41     for(int i=1;i<=n;++i)sum[i]=n-sa[i]+1-h[i]+sum[i-1];//printf("%d\n",lcp(7,10));
42 //    for(int i=1;i<=n;++i)printf("%d ",h[i]);puts("");
43     int l=1,r=sum[n],RK,i=1;for(int j=1;j<=n;++j)if(!h[j])l=sum[j-1]+1;
44     while(l<=r)if(chk(l+r>>1))RK=r=l+r>>1,r--;else l=(l+r>>1)+1;
45     for(;sum[i]for(int j=sa[i];j<=n-sum[i]+RK;++j)putchar(s[j]);puts("");//printf("%d\n",RK);
46 }
View Code

 

股市的预测:

$Description:$

墨墨的妈妈热爱炒股,她要求墨墨为她编写一个软件,预测某只股票未来的走势。股票折线图是研究股票的必备工具,它通过一张时间与股票的价位的函数图像清晰地展示了股票的走势情况。经过长时间的观测,墨墨发现很多股票都有如下的规律:之前的走势很可能在短时间内重现!如图可以看到这只股票A部分的股价和C部分的股价的走势如出一辙。通过这个观测,墨墨认为他可能找到了一个预测股票未来走势的方法。进一步的研究可是难住了墨墨,他本想试图统计B部分的长度与发生这种情况的概率关系,不过由于数据量过于庞大,依赖人脑的力量难以完成,于是墨墨找到了善于编程的你,请你帮他找一找给定重现的间隔(B部分的长度),有多少个时间段满足首尾部分的走势完全相同呢?当然,首尾部分的长度不能为零。4≤N≤50000

思路非常神仙。

按照题意,差分。然后就可以写暴力了。

考虑有关参数:就是A的长度与起始位置。

但是答案最多是$O(n^2)$级别的。任何$ans++$的方法都会$TLE$。

所以考虑,枚举A的长度len之后如何考虑A的起始位置。

进行匹配的话,i和i+len+m同一段的匹配长度如果大于len那么就可以贡献答案。每一个长度为len的子串都可以贡献答案。

所以我们每隔len个点枚举一个i,求出前后的最长匹配长度,分别对len取min防止重复,这样贡献答案就可以了。

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define S 55555
 5 mapint>M;
 6 int n,k,s[S],x[S],y[S],c[S],sa[2][S],rk[2][S],h[2][S],ans,ST[2][19][S],hb[S];
 7 ll a[S],b[S];
 8 void Suffix_Array(int*s,int*x,int*y,int*sa,int*rk,int*h,int n,int m){
 9     for(int i=1;i<=m;++i)c[i]=0;
10     for(int i=1;i<=n;++i)c[x[i]=s[i]]++;
11     for(int i=1;i<=m;++i)c[i]+=c[i-1];
12     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
13     for(int len=1,num;num=0,n^m;len<<=1){
14         for(int i=n-len+1;i<=n;++i)y[++num]=i;
15         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
16         for(int i=1;i<=m;++i)c[i]=0;
17         for(int i=1;i<=n;++i)c[x[i]]++;
18         for(int i=1;i<=m;++i)c[i]+=c[i-1];
19         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
20         swap(x,y);x[sa[1]]=m=1;
21         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
22     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
23     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(s[i+k]==s[sa[rk[i]-1]+k])k++;
24 }
25 void build_ST(int o){
26     for(int i=2;i<=n;++i)ST[o][0][i]=h[o][i],hb[i]=hb[i>>1]+1;
27     for(int t=1;t<=18;++t)for(int i=1;i+(1<1<=n;++i)ST[o][t][i]=min(ST[o][t-1][i],ST[o][t-1][i+(1<1)]);
28 }
29 int ask(int o,int l,int r){int B=hb[r-l+1];return min(ST[o][B][l],ST[o][B][r-(1<1]);}
30 int lcp(int o,int i,int j){return ask(o,min(rk[o][i],rk[o][j])+1,max(rk[o][i],rk[o][j]));}
31 int main(){
32     scanf("%d%d",&n,&k);for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
33     for(int i=1;i1]-a[i];n--;
34     sort(b+1,b+n+1);
35     for(int i=1;i<=n;++i)M[b[i]]=i;
36     for(int i=1;i<=n;++i)s[i]=M[a[i]];
37     Suffix_Array(s,x,y,sa[0],rk[0],h[0],n,n+1);build_ST(0);
38     reverse(s+1,s+1+n);
39     Suffix_Array(s,x,y,sa[1],rk[1],h[1],n,n+1);build_ST(1);
40     for(int len=1;lenfor(int i=1;i+len+k<=n;i+=len){
41         int LCP=min(len,lcp(0,i,i+len+k)),LCS=min(len,lcp(1,n-i-len-k+1,n-i+1));
42         ans+=max(0,LCP+LCS-len);//printf("%d %d %d %d %d\n",len,i,LCP,LCS,ans);
43     }cout<endl;
44 }
View Code

 

SvT:

$Description:$

(我并不想告诉你题目名字是什么鬼)

有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].

现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍$S \le 5 \times 10^5,\sum t \le 3 \times 10^6$

听NC说题目是后缀虚树。

对于每个询问全部读入后去重按照rank排序,然后两遍单调栈扫出控制区间。

 1 #include
 2 using namespace std;
 3 #define S 500005
 4 char s[S];int x[S],y[S],h[S],sa[S],rk[S],n,m,ST[20][S],hb[S],p[S],L[S],R[S],q[S],top,c[S];
 5 void Suffix_Array(char*a,int*x,int*y,int n,int m){
 6     for(int i=1;i<=m;++i)c[i]=0;
 7     for(int i=1;i<=n;++i)c[x[i]=a[i]-'a'+1]++;
 8     for(int i=1;i<=m;++i)c[i]+=c[i-1];
 9     for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
10     for(int len=1,num;num=0,n^m;len<<=1){
11         for(int i=n-len+1;i<=n;++i)y[++num]=i;
12         for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len;
13         for(int i=1;i<=m;++i)c[i]=0;
14         for(int i=1;i<=n;++i)c[x[i]]++;
15         for(int i=1;i<=m;++i)c[i]+=c[i-1];
16         for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
17         swap(x,y);x[sa[1]]=m=1;
18         for(int i=2;i<=n;++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&y[sa[i]+len]==y[sa[i-1]+len]?m:++m;
19     }for(int i=1;i<=n;++i)rk[sa[i]]=i;
20     for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:k)while(a[i+k]==a[sa[rk[i]-1]+k])k++;
21 }
22 void build_ST(){
23     for(int i=2;i<=n;++i)hb[i]=hb[i>>1]+1,ST[0][i]=h[i];
24     for(int t=1;t<=19;++t)for(int i=1;i+(1<1<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<1)]);
25 }
26 int query(int l,int r){int B=hb[r-l+1];return min(ST[B][l],ST[B][r-(1<1]);}
27 int main(){
28     scanf("%d%d%s",&n,&m,s+1);Suffix_Array(s,x,y,n,26);build_ST();
29     for(int i=1,t;i<=m;++i){
30         scanf("%d",&t);long long ans=0;
31         for(int j=1,x;j<=t;++j)scanf("%d",&x),p[j]=rk[x];
32         sort(p+1,p+1+t);t=unique(p+1,p+1+t)-p-1;
33         for(int i=1;i1,p[i+1]);
34         for(int j=1;jj){
35             while(top&&p[q[top]]>=p[j])R[q[top--]]=j-1;
36             q[++top]=j;
37         }while(top)R[q[top--]]=t-1;
38         for(int j=t-1;j;--j){
39             while(p[q[top]]>p[j])L[q[top--]]=j+1;
40             q[++top]=j;
41         }while(top)L[q[top--]]=1;
42         for(int j=1;j1)*p[j];
43         printf("%lld\n",ans);
44     }
45 }
View Code

我并不想告诉你题目名字是什么鬼)

有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].

现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍.

你可能感兴趣的:(「专题总结」后缀数组1~2)