• [loj3103]节日庆典


    约定:以下字符串下标从1开始

    对于字符串$s_{1}$和$s_{2}$,定义以下信息——

    定义$s_{1}approx s_{2}$当且仅当$s_{1}[1,l]=s_{2}[1,l]$(其中$l=min(|s_{1}|,|s_{2}|)$)

    定义$s_{1}ll s_{2}$当且仅当$s_{1}<s_{2}$且$s_{1} otapprox s_{2}$,$s_{1}gg s_{2}$当且仅当$s_{1}>s_{2}$且$s_{1} otapprox s_{2}$

    结论:若$s=caab$,则$f(s) e |c|+|a|+1$(其中$a,b,c$为任意字符串,且$a,b$非空)

    考虑取另外$|c|+1$和$|c|+2|a|+1$的位置,即求证$abcale aabc$或$abca<bcaa$

    (前者取等号,因为当相等时,$f(s)$会取靠前的位置)

    当$bcale abc$时,前者显然成立;当$bca>abc$时,后者显然成立

    (另外,当$b$为空时此结论也成立,但本文中并不需要利用此性质)

    $forall xin [1,i]$,$xin S_{i}$当且仅当其满足:

    1.$forall 1le yle i,s[x,i] otll s[y,i]$

    2.$forall 1le y<x且2x-yle i,s[y,i] otapprox s[x,i]$

    (这个条件即等价于不存在$s[1,i]=caab$使得$f(s)=|c|+|a|+1$)

    显然,有$f(s[1,i])in S_{i}$,下面考虑$|S_{i}|$的大小——

    结论:$|S_{i}|sim o(log n)$

    若$x,yin S_{i}$(不妨假设$x<y$),根据第一个性质,则有$s[x,i]approx s[y,i]$

    若$2y-xle i$,即可以得到$s[x,y)=s[y,2y-x)$,与第2个性质矛盾

    因此$2y-x>i$,也即$2|s[y,i]|le |s[x,i]|$,因此将$S_{i}$中的$x$写作$i-x+1$从小到大排列后,后一项大于等于前一项的两倍且值域为$[1,i]$,即有$|S_{i}|sim o(log n)$

    下面,考虑如何求$S_{i}$,显然$S_{i}subseteq S_{i-1}cup {i}$,从小到大枚举$S_{i-1}cup {i}$中的元素$x$,并判定能否加入$S_{i}$中

    关于这里,判定只需要考虑$S_{i}$中的元素(正确性显然),更具体的即如下做法——

    初始$S_{i}$为空,若$S_{i}$为空则直接加入,否则令$y$为当前$S_{i}$中的最大值,考虑$s_{i}$和$s_{y+i-x}$的关系:

    1.$s_{i}>s_{y+i-x}$,不加入$x$

    2.$s_{i}<s_{y+i-x}$,令$S_{i}={x}$

    3.$s_{i}=s_{y+i-x}$,若$2x-y>i$则加入$x$

    初始为$S_{0}=empty$,综上即可求出$S_{i}$,且复杂度为$o(nlog n)$

    最后,考虑如何求$f(s[1,i])$,根据$f(s[1,i])in S_{i}$,可以暴力枚举$S_{i}$中的元素并判定

    下面,我们要快速判定$s[x,i]+s[1,x)$和$s[y,i]+s[1,y)$的大小关系(不妨假设$x<y$),由于$x,yin S_{i}$,因此有$s[x,i]approx s[y,i]$,也即$s[y,i]$是$s[x,i]$的前缀

    换言之,即判定$s(i-(y-x),i]+s[1,x)$和$s[1,y)$的大小关系,考虑求lcp后判定第一个不同的字符,那么问题即求$s$某个后缀与$s$的lcp长度,有以下三种做法:

    1.二分+哈希可以做到$o(log n)$,但总复杂度即变为$o(nlog^{2}n)$,无法通过

    2.后缀数组,复杂度为$o(nlog n)$,常数太大,无法通过

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define N 3000005
     4 int n,ans,rk[N<<1],a[N],b[N],c[N],tot[N],sa[N],h[N],v[N],vv[N],num[11];
     5 char s[N];
     6 void write(int x){
     7     while (x){
     8         num[++num[0]]=x%10;
     9         x/=10;
    10     }
    11     for(;num[0];num[0]--)putchar(num[num[0]]+'0');
    12 }
    13 int main(){
    14     scanf("%s",s+1);
    15     n=strlen(s+1);
    16     for(int i=1;i<=n;i++)rk[i]=s[i]-'a'+1;
    17     int m=26;
    18     for(int i=1;;i<<=1){
    19         memset(tot,0,sizeof(tot));
    20         for(int j=1;j<=n;j++)tot[rk[j+i]]++;
    21         for(int j=1;j<=m;j++)tot[j]+=tot[j-1];
    22         for(int j=1;j<=n;j++)a[tot[rk[j+i]]--]=j;
    23         memset(tot,0,sizeof(tot));
    24         for(int j=1;j<=n;j++)tot[rk[j]]++;
    25         for(int j=2;j<=m;j++)tot[j]+=tot[j-1];
    26         for(int j=n;j;j--)b[tot[rk[a[j]]]--]=a[j];
    27         m=0;
    28         for(int j=1;j<=n;j++){
    29             if ((j==1)||(rk[b[j]]!=rk[b[j-1]])||(rk[b[j]+i]!=rk[b[j-1]+i]))m++;
    30             c[b[j]]=m;
    31         }
    32         memcpy(rk,c,sizeof(c));
    33         if (m==n)break;
    34     }
    35     for(int i=1;i<=n;i++)sa[rk[i]]=i;
    36     for(int i=1,k=0;i<=n;i++,k-=(k>0)){
    37         while ((i+k<=n)&&(sa[rk[i]+1]+k<=n)&&(s[i+k]==s[sa[rk[i]+1]+k]))k++;
    38         h[rk[i]]=k;
    39     }
    40     for(int i=rk[1]-2;i;i--)h[i]=min(h[i],h[i+1]);
    41     for(int i=n;i>rk[1];i--)h[i]=h[i-1];
    42     h[rk[1]]=n;
    43     for(int i=rk[1]+1;i<=n;i++)h[i]=min(h[i],h[i-1]);
    44     memcpy(c,h,sizeof(c));
    45     for(int i=1;i<=n;i++)h[i]=c[rk[i]];
    46     for(int i=1;i<=n;i++){
    47         for(int j=0;j<=v[0];j++)vv[j]=v[j];
    48         vv[++vv[0]]=i;
    49         v[0]=0;
    50         for(int j=1;j<=vv[0];j++){
    51             if (!v[0])v[++v[0]]=vv[j];
    52             else{
    53                 int x=vv[j],y=v[v[0]];
    54                 if (s[i]<s[y+i-x])v[0]=0;
    55                 if ((s[i]<s[y+i-x])||(s[i]==s[y+i-x])&&(2*x-y>i))v[++v[0]]=x;
    56             }
    57         }
    58         ans=v[1];
    59         for(int j=2;j<=v[0];j++){
    60             int x=ans,y=v[j],l=h[i-(y-x)+1];
    61             if (l<y-x){
    62                 if (s[i-(y-x)+l+1]>s[l+1])ans=y;
    63             }
    64             else{
    65                 l=h[y-x+1];
    66                 if ((l<x-1)&&(s[y-x+l]<s[l+1]))ans=y;
    67             }
    68         }
    69         write(ans);
    70         if (i<n)putchar(' ');
    71     }
    72 } 
    View Code

    3.exkmp,下面将讲述其做法——

    定义数组$z_{i}=lcp(s[i,|s|],s)$(特别的,强制$z_{1}=0$)

    考虑求$z_{i}$,维护一个区间$[l,r]$,其中$r=max_{1le j<i}z_{j}+j-1$,$l$为对应此$r$的最小的$j$

    若$ile r$,根据$s[l,r]=s[1,r-l+1]$,有$s[i,r]=s[l-i+1,r-l+1]$,此时对$z_{i-l+1}$分类讨论:

    1.$z_{i-l+1}<r-i+1$,则$z_{i}=z_{i-l+1}$(如果再增大,即$z_{i-l+1}$也能增大)

    2.$z_{i-l+1}ge r-i+1$,则令$z_{i}=r-i+1$,并暴力增大$z_{i}$扩展

    若$i>r$,则令$z_{i}=0$,并暴力增大$z_{i}$扩展

    最后,再用$[j,z_{j}+j)$去更新$[l,r]$(当$r<z_{j}+j-1$时)

    关于这一做法的正确性显然,考虑时间复杂度:对于两个暴力增大$z_{i}$扩展的情况,都是初始$i+z_{i}-1=r$,之后不断增加$z_{i}$,最终仍令$r=i+z_{i}-1$

    换言之,每一次增加$z_{i}$,不妨同时令$r$增加1,那么显然总复杂度为$o(n)$

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define N 3000005
     4 int n,ans,h[N],v[N],vv[N],num[11];
     5 char s[N];
     6 void write(int x){
     7     while (x){
     8         num[++num[0]]=x%10;
     9         x/=10;
    10     }
    11     for(;num[0];num[0]--)putchar(num[num[0]]+'0');
    12 }
    13 int main(){
    14     scanf("%s",s+1);
    15     n=strlen(s+1);
    16     int l=1,r=1;
    17     for(int i=2;i<=n;i++){
    18         if (i<=r){
    19             if (h[i-l+1]<r-i+1)h[i]=h[i-l+1];
    20             else h[i]=r-i+1;
    21         }
    22         while ((i+h[i]<=n)&&(s[i+h[i]]==s[h[i]+1]))h[i]++;
    23         if (i+h[i]-1>r){
    24             l=i;
    25             r=i+h[i]-1;
    26         }
    27     }
    28     h[1]=n;
    29     for(int i=1;i<=n;i++){
    30         for(int j=0;j<=v[0];j++)vv[j]=v[j];
    31         vv[++vv[0]]=i;
    32         v[0]=0;
    33         for(int j=1;j<=vv[0];j++){
    34             if (!v[0])v[++v[0]]=vv[j];
    35             else{
    36                 int x=vv[j],y=v[v[0]];
    37                 if (s[i]<s[y+i-x])v[0]=0;
    38                 if ((s[i]<s[y+i-x])||(s[i]==s[y+i-x])&&(2*x-y>i))v[++v[0]]=x;
    39             }
    40         }
    41         ans=v[1];
    42         for(int j=2;j<=v[0];j++){
    43             int x=ans,y=v[j],l=h[i-(y-x)+1];
    44             if (l<y-x){
    45                 if (s[i-(y-x)+l+1]>s[l+1])ans=y;
    46             }
    47             else{
    48                 l=h[y-x+1];
    49                 if ((l<x-1)&&(s[y-x+l]<s[l+1]))ans=y;
    50             }
    51         }
    52         write(ans);
    53         if (i<n)putchar(' ');
    54     }
    55 } 
    View Code
  • 相关阅读:
    洛谷 P5564: [Celeste-B]Say Goodbye
    LOJ 3185: 「CEOI2018」斐波那契表示法
    Codeforces 749E: Inversions After Shuffle
    C#之在treeview中鼠标点击的所选的节点触发事件
    C#中选中指定文件并读取类似ini文件的内容
    免费的EmBitz可替代Keil MDK开发STM32、NXP项目
    C#创建子线程,子线程使用委托更新控件
    C#调用C++生成的动态链接库DLL
    C#之菜单控件、主窗体打开子窗体、GroupBox控件使用
    在Linux下用CANopenSocket协议模拟CAN总线通讯
  • 原文地址:https://www.cnblogs.com/PYWBKTDA/p/14785609.html
Copyright © 2020-2023  润新知