$SA$后缀数组复健
差异:
首先拆柿子:
$$\frac{\sum_{i=1}^{n}{(n-2)*i+\frac{n*(n+1)}{2}}}{2}-\sum_{1\leq i< j\leq n}{2*lcp(i,j)}$$
$$\frac{n*(n-1)*(n+1)}{2}-\sum_{1\leq i< j\leq n}{2*lcp(i,j)}$$
后面的$lcp$可以用单调栈求出每个$height(i)$控制的区间,$O(n)$解决。
注意一个$height$对应区间长度的至少是$2$,并且$height(1)$无用。
Sandy的卡片:
把字符串加上分割符建$sa$,然后二分长度,$O(n*logn)$。
相似子串:
手玩发现子串的排名和子串所在的后缀有关。
把每个后缀串控制的排名范围求出,即可$lower\_bound$出排名对应的子串。
正反建$sa$,$ST$表维护公共前缀即可。
品酒大会:
第一问和差异一样,第二问直接$mid$的两边$ST$表。
因为“$r$相似同时也是$r-1$相似的”,所以要后缀取$\max$。
注意数据结构的操作是套在$rk$上的,下标取值一定要写对。
喵星球上的点名:
数据比较水,暴扫可过。
看网上有十几种做法,字符串家族八仙过海。
这里采纳了一个很实用很清真的树状数组做法。
第一问:
数颜色,可用主席树。
树状数组的做法是先离线,求每个颜色的$pre$。
然后边扫边统计答案,到某个颜色时把$pre$上的贡献删掉,前缀做差得答案。
原理是只留下了一个区间同种颜色的最后一个。
第二问:
也是边扫边用树状数组。
扫到位置$i$,把询问区间左端点在$i$上的加入。
$i$与$pre[i]$前缀和相减得到左端点在这之间的个数。
接下来把区间右端点在$i$的左端点的位置贡献减去,保证了不会统计到夹在$i$到$pre[i]$的区间。
神仙树状数组做法。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<vector> 5 #define MAXN 490001 6 #define lowbit(x) (x&-x) 7 using namespace std; 8 inline int read(){ 9 int s=0,w=0;char ch=getchar(); 10 while(ch<'0'||ch>'9')w|=(ch=='-'),ch=getchar(); 11 while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar(); 12 return w?-s:s; 13 } 14 #define kd (read()) 15 int x[MAXN],y[MAXN],c[MAXN]; 16 int a[MAXN],sa[MAXN],rk[MAXN],h[MAXN]; 17 int n,m; 18 int f[19][MAXN]; 19 int LOG[MAXN]; 20 inline int Getmn(int l,int r){ 21 if(r<l)return 0x7fffffff; 22 int len=LOG[r-l+1]; 23 return min(f[len][l],f[len][r-(1<<len)+1]); 24 } 25 void init(){ 26 m=10002; 27 for(int i=1;i<=m;++i)c[i]=0; 28 for(int i=1;i<=n;++i)++c[x[i]=a[i]]; 29 for(int i=1;i<=m;++i)c[i]+=c[i-1]; 30 for(int i=1;i<=n;++i)sa[c[x[i]]--]=i; 31 for(int len=1;len<=n;len<<=1){ 32 int num=0; 33 for(int i=n-len+1;i<=n;++i)y[++num]=i; 34 for(int i=1;i<=n;++i)if(sa[i]>len)y[++num]=sa[i]-len; 35 for(int i=1;i<=m;++i)c[i]=0; 36 for(int i=1;i<=n;++i)++c[x[i]]; 37 for(int i=1;i<=m;++i)c[i]+=c[i-1]; 38 for(int i=n;i>=1;--i)sa[c[x[y[i]]]--]=y[i]; 39 for(int i=1;i<=n;++i)y[i]=x[i],x[i]=0; 40 x[sa[1]]=m=1; 41 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; 42 if(m==n)break; 43 } 44 for(int i=1;i<=n;++i)rk[sa[i]]=i; 45 for(int i=1,k=0;i<=n;h[rk[i++]]=k,k=k?k-1:0) 46 while(a[i+k]==a[sa[rk[i]-1]+k])++k; 47 for(int i=1;i<=n;++i)f[0][i]=h[i]; 48 for(int j=1;j<=18;++j) 49 for(int i=1;i+(1<<j)-1<=n;++i) 50 f[j][i]=min(f[j-1][i],f[j-1][i+(1<<j-1)]); 51 return ; 52 } 53 int N,M; 54 int nw[MAXN],pre[MAXN]; 55 int belong[MAXN],id[MAXN],Len[MAXN]; 56 int an1[MAXN],an2[MAXN]; 57 int C[MAXN]; 58 inline void addC(int pos,int val){ 59 if(!pos)return ; 60 for(;pos<=n;pos+=lowbit(pos))C[pos]+=val; 61 return ; 62 } 63 inline int Getsm(int pos){ 64 int res=0; 65 for(;pos;pos-=lowbit(pos))res+=C[pos]; 66 return res; 67 } 68 struct node{ 69 int l,r,idx; 70 }; 71 vector<node >td[MAXN]; 72 vector<int >dd[MAXN]; 73 int lei[MAXN]; 74 int main(){ 75 //freopen("da.in","r",stdin); 76 //freopen("2.out","w",stdout); 77 78 N=kd,M=kd; 79 for(int i=1,llen,tmp;i<=N;++i){ 80 llen=kd; 81 while(llen--)tmp=kd+1,a[++n]=tmp,belong[n]=i; 82 a[++n]=10002; 83 llen=kd; 84 while(llen--)tmp=kd+1,a[++n]=tmp,belong[n]=i; 85 a[++n]=10002; 86 } 87 for(int i=1,llen,tmp;i<=M;++i){ 88 id[i]=n+1;Len[i]=llen=kd; 89 while(llen--)tmp=kd+1,a[++n]=tmp; 90 a[++n]=10002; 91 } 92 for(int i=2;i<=n;++i)LOG[i]=LOG[i>>1]+1; 93 init(); 94 for(int i=1;i<=M;++i){ 95 int l=1,r=rk[id[i]],posl=0,posr=0; 96 while(r-l>1){ 97 int mid=l+r>>1; 98 if(Getmn(mid+1,rk[id[i]])>=Len[i])r=mid; 99 else l=mid; 100 } 101 if(Getmn(l+1,rk[id[i]])>=Len[i])posl=l; 102 else posl=r; 103 l=rk[id[i]],r=n; 104 while(r-l>1){ 105 int mid=l+r>>1; 106 if(Getmn(rk[id[i]]+1,mid)>=Len[i])l=mid; 107 else r=mid; 108 } 109 if(Getmn(rk[id[i]]+1,r)>=Len[i])posr=r; 110 else posr=l; 111 td[posr].push_back((node){posl,posr,i}); 112 dd[posr].push_back(posl); 113 ++lei[posl]; 114 //cout<<posl<<" "<<posr<<endl; 115 } 116 for(int i=1;i<=n;++i) 117 if(belong[sa[i]]){ 118 pre[i]=nw[belong[sa[i]]]; 119 nw[belong[sa[i]]]=i; 120 } 121 for(int i=1;i<=n;++i){ 122 if(belong[sa[i]]){ 123 addC(pre[i],-1); 124 addC(i,1); 125 } 126 for(int k=0;k<td[i].size();++k){ 127 node tmp=td[i][k]; 128 an1[tmp.idx]=Getsm(tmp.r)-Getsm(tmp.l-1); 129 } 130 } 131 for(int i=1;i<=M;++i)printf("%d\n",an1[i]); 132 for(int i=0;i<=n;++i)C[i]=0; 133 for(int i=1;i<=n;++i){ 134 addC(i,lei[i]); 135 if(belong[sa[i]]) 136 an2[belong[sa[i]]]+=Getsm(i)-Getsm(pre[i]); 137 for(int k=0;k<dd[i].size();++k)addC(dd[i][k],-1); 138 } 139 for(int i=1;i<=N;++i)printf("%d ",an2[i]); 140 puts(""); 141 142 return 0; 143 }
跳蚤:
上来思路偏了,莫名想起模拟测试里的类似单调性优化$dp$。
然后想怎么快速求出一个区间的贡献,没法求
然后颓了一眼标签,是二分。
思路来了,因为跑完$sa$,可以得到子串的排名。
考虑二分这个排名是否可行。
那就要看是否能$check$,以及合法的时间复杂度。
发现反向贪心是可以的。
至于正向为何不太行,是因为在考虑一个点能否加入,就要看他对字典序的最大贡献,最大贡献一定是右端点在自己所在的块的右边界上。而只有反向才能确定这个右边界。
然后是解决如何判断一个点能否加入,即是看此点$x$和右边界$now$的子串的排名和二分的排名对比。
一开始我想再来一个二分此子串的排名。
又看了发题解,发现$lcp$有大用处,可以先得到二分排名的串,和要判断的串求$lcp+1$位字典序比较即可。