「專題總結」后綴數組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<cstdio>
 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<bits/stdc++.h>
 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/500<x.l/500: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<<t)-1<=c;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<<t-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<<i&&ST[i][l-(1<<i)]>=H)l-=1<<i;
35         if(r+(1<<i)<=n&&ST[i][r+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<q[T].l)del(l++);
72         while(r>q[T].r)del(r--);
73         while(l>q[T].l)add(--l);
74         while(r<q[T].r)add(++r);
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<bits/stdc++.h>
 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<<t-1)<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<<t-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<<B)+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<cstdio>
 2 #include<algorithm>
 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(i<j)的后綴的lcp為min{height[k]|i+1≤k≤j}
27     //轉化:已知一段序列,求所有字串的最小值的和
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<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<iostream>
 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<<i-1)]),
16         st2[i][j]=min(st2[i-1][j],st2[i-1][j+(1<<i-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<l;++i)c[x[i]=s[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(le<r-1){
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]=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<bits/stdc++.h>
 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<n;++i)printf("%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<bits/stdc++.h>
 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<bits/stdc++.h>
 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<<t)-1<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<<t-1)]);
25 }
26 int ask(int l,int r){int B=hb[r-l+1];return min(ST[B][l],ST[B][r-(1<<B)+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]<Rk;++I);len=n-sa[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]<RK;++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<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 #define S 55555
 5 map<ll,int>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<<t)-1<=n;++i)ST[o][t][i]=min(ST[o][t-1][i],ST[o][t-1][i+(1<<t-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<<B)+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;i<n;++i)a[i]=b[i]=a[i+1]-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;len<n;++len)for(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<<ans<<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<bits/stdc++.h>
 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<<t)-1<=n;++i)ST[t][i]=min(ST[t-1][i],ST[t-1][i+(1<<t-1)]);
25 }
26 int query(int l,int r){int B=hb[r-l+1];return min(ST[B][l],ST[B][r-(1<<B)+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;i<t;++i)p[i]=query(p[i]+1,p[i+1]);
34         for(int j=1;j<t;++j){
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;j<t;++j)ans+=(j-L[j]+1ll)*(R[j]-j+1)*p[j];
43         printf("%lld\n",ans);
44     }
45 }
View Code

我並不想告訴你題目名字是什么鬼)

有一個長度為n的僅包含小寫字母的字符串S,下標范圍為[1,n].

現在有若干組詢問,對於每一個詢問,我們給出若干個后綴(以其在S中出現的起始位置來表示),求這些后綴兩兩之間的LCP(LongestCommonPrefix)的長度之和.一對后綴之間的LCP長度僅統計一遍.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM