$$AVICII$$

$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 }
View Code

 


跳蚤:

  上来思路偏了,莫名想起模拟测试里的类似单调性优化$dp$。

  然后想怎么快速求出一个区间的贡献,没法求

  然后颓了一眼标签,是二分。

  思路来了,因为跑完$sa$,可以得到子串的排名。

  考虑二分这个排名是否可行。

  那就要看是否能$check$,以及合法的时间复杂度。

  发现反向贪心是可以的。

  至于正向为何不太行,是因为在考虑一个点能否加入,就要看他对字典序的最大贡献,最大贡献一定是右端点在自己所在的块的右边界上。而只有反向才能确定这个右边界。

  然后是解决如何判断一个点能否加入,即是看此点$x$和右边界$now$的子串的排名和二分的排名对比。

  一开始我想再来一个二分此子串的排名。

  又看了发题解,发现$lcp$有大用处,可以先得到二分排名的串,和要判断的串求$lcp+1$位字典序比较即可。

 

 

 

  

 

posted @ 2022-07-04 11:40  bootpuss  阅读(83)  评论(0编辑  收藏  举报