zoukankan      html  css  js  c++  java
  • NOI 2016 优秀的拆分 (后缀数组+差分)

    题目大意:给你一个字符串,求所有子串的所有优秀拆分总和,优秀的拆分被定义为一个字符串可以被拆分成4个子串,形如$AABB$,其中$AA$相同,$BB$相同,$AB$也可以相同

    作为一道国赛题,95分竟然就这么给我们了!只是一个$NOIP$难度的哈希套$DP$啊......

    95分就是从后往前找,统计$AA$串,每次统计一下从这个位置开始的所有子串 和 紧随其后的等长串 相同的个数$sum$

    $hash(i,i+j-1)==hash(i+j,i+2*j-1) sum[i]++$

    然后再统计$BB$串,就是加上在这两个串之后的位置的$sum$值,这样就统计出了合法拆分数

    $dp[i]+=sum[i+2*j]$

    95分就这样到手啦,竟然连自然溢出hash都不卡

     1 #include <cmath>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define ll long long 
     6 #define ull unsigned long long 
     7 #define N 2050
     8 #define seed 233
     9 #define idx(x) (x-'a'+1)
    10 using namespace std;
    11 //re
    12 int T,len;
    13 char str[N];
    14 int dp[N],sum[N];
    15 ull hsh[N],sp[N];
    16 ull ghsh(int x,int y) {return hsh[y]-hsh[x-1]*sp[y-x+1];}
    17 void clr()
    18 {
    19     memset(dp,0,sizeof(dp));
    20     memset(sp,0,sizeof(sp));
    21     memset(sum,0,sizeof(sum));
    22     memset(hsh,0,sizeof(hsh));
    23 }
    24 int main()
    25 {
    26     scanf("%d",&T);
    27     while(T--)
    28     {
    29         clr();
    30         scanf("%s",str+1);
    31         len=strlen(str+1);
    32         sp[0]=1;
    33         for(int i=1;i<=len;i++)
    34             hsh[i]=hsh[i-1]*seed+idx(str[i]),
    35             sp[i]=sp[i-1]*seed;
    36         for(int i=len-1;i>=1;i--)
    37         {
    38             for(int j=1;i+2*j-1<=len;j++)
    39             {
    40                 if(ghsh(i,i+j-1)==ghsh(i+j,i+2*j-1)){
    41                     dp[i]++;
    42                     sum[i]+=dp[i+2*j];
    43                 }
    44             }
    45         }
    46         int ans=0;
    47         for(int i=1;i<=len;i++)
    48             ans+=sum[i];
    49         printf("%d
    ",ans);
    50     }
    51     return 0;
    52 }

    接下来才是本题的重点!$NOI$岂是你想$AK$就$AK$的!出题人可能是想针对某位巨佬

    不看题解这个正解思路真是很难想到......

    思路大概是这样的,其实我们每次只需要统计$AA$串的数量就行了,因为$AABB$串的数量等于,在$i-1$位结束的$AA$串的数量乘以在第$i$位开始的$AA$串的数量,用公式表示就是

    sum_{i=2}^{n} ed[i-1]*st[i]

    接下来就是统计$st$和$ed$了,感觉正解的思路很神

    先用后缀数组+$ST$表处理出任意两个位置,作为后缀串开头的$LCP$以及作为前缀串末尾的$LCS$,求$LCS$可以把串反着读再去套$SA$

    每次选取一个$A$串的长度$len$,再在原串每隔$len$的位置设一个关键点

    然后会发现一个神奇的性质,任意一个长度为$2*len$的串必然经过且仅经过2个关键点!

    接下来统计经过所有长度为$2*len$所有$AA$串,比如选定两个关键点$a,b$,且$a+len=b$

    如果把$a$的和b$相同的前缀和后缀拼在一起,且拼完之后长度>=len,说明存在至少一个$AA$串

    可以把$AA$串想象成一个块,在两个关键点上移动,块的左端点不能大于$a$,块的右端点不能小于$b$,块不能移动到上一个或者下一个关键点的位置,块内必须是$AA$串,这样,块所有能移动的位置-块的长度$len$,就是经过$ab$两个关键点的所有$AA$串数

    时间变成O(nlogn),$logn$是调和级数的近似值

    细节比较多需要仔细思考

      1 #include <cmath>
      2 #include <cstdio>
      3 #include <cstring>
      4 #include <algorithm>
      5 #define ll long long 
      6 #define N 30100
      7 #define seed 233
      8 #define idx(x) (x-'a'+1)
      9 using namespace std;
     10 //re
     11 int T,len;
     12 int lg[N];
     13 ll st[N],ed[N],S[N],E[N];
     14 struct suffix{
     15     char str[N];
     16     int sa[N],tr[N],rk[N],hs[N],h[N],f[N][15],len; 
     17     bool check(int k,int x,int y){
     18         if(x+k>len||y+k>len) return 0;
     19         else return (rk[x]==rk[y]&&rk[x+k]==rk[y+k])?1:0;
     20     }
     21     void get_suffix()
     22     {
     23         int cnt=0,i;len=strlen(str+1);
     24         for(i=1;i<=len;i++) hs[str[i]]++;
     25         for(i=1;i<=127;i++) if(hs[i]) tr[i]=++cnt;
     26         for(i=1;i<=127;i++) hs[i]+=hs[i-1];
     27         for(i=1;i<=len;i++) rk[i]=tr[str[i]],sa[hs[str[i]]--]=i;
     28         for(int k=1;cnt<len;k<<=1)
     29         {
     30             for(i=1;i<=cnt;i++) hs[i]=0;
     31             for(i=1;i<=len;i++) hs[rk[i]]++;
     32             for(i=1;i<=cnt;i++) hs[i]+=hs[i-1];
     33             for(i=len;i>=1;i--) if(sa[i]>k) tr[sa[i]-k]=hs[rk[sa[i]-k]]--;
     34             for(i=1;i<=k;i++) tr[len-i+1]=hs[rk[len-i+1]]--;
     35             for(i=1;i<=len;i++) sa[tr[i]]=i;
     36             for(i=1,cnt=0;i<=len;i++) tr[sa[i]]=check(k,sa[i],sa[i-1])?cnt:++cnt;
     37             for(i=1;i<=len;i++) rk[i]=tr[i];
     38         }
     39         for(i=1;i<=len;i++)
     40         {
     41             if(rk[i]==1) continue;
     42             for(int j=max(1,h[rk[i-1]]-1);;j++)
     43                 if(str[i+j-1]==str[sa[rk[i]-1]+j-1]) h[rk[i]]=j;
     44                 else break;
     45         }
     46     }
     47     void get_ST()
     48     {
     49         for(int i=1;i<=len;i++) 
     50             f[i][0]=h[i];
     51         for(int k=1;(1<<k)<=len;k++)
     52             for(int i=1;i+(1<<k)-1<=len;i++)
     53                 f[i][k]=min(f[i][k-1],f[i+(1<<(k-1))][k-1]);
     54     }
     55     int query(int x,int y)
     56         {int L=y-x+1;
     57         return min(f[x][lg[L]],f[y-(1<<lg[L])+1][lg[L]]);}
     58 }p,s;
     59 void clr()
     60 {
     61     memset(S,0,sizeof(S)),memset(st,0,sizeof(st));
     62     memset(E,0,sizeof(E)),memset(ed,0,sizeof(ed));
     63     memset(&p,0,sizeof(p)),memset(&s,0,sizeof(s));
     64 }
     65 int main()
     66 {
     67     scanf("%d",&T);
     68     for(int i=2;i<=30010;i++)
     69         lg[i]=lg[i>>1]+1;
     70     while(T--)
     71     {
     72         clr();
     73         scanf("%s",s.str+1);
     74         int len=strlen(s.str+1);
     75         for(int i=1;i<=len;i++) 
     76             p.str[i]=s.str[len-i+1];
     77         s.get_suffix();
     78         s.get_ST();
     79         p.get_suffix();
     80         p.get_ST();
     81         int sx,sy,px,py,ss,sp,l,r;
     82         for(int i=1;i<=len;i++)
     83         {
     84             for(int j=i+1;j<=len;j+=i)
     85             {
     86                 sx=s.rk[j-i+1],sy=s.rk[j+1];
     87                 if(sx>sy) swap(sx,sy);
     88                 px=p.rk[len-(j-i)+1],py=p.rk[len-j+1];
     89                 if(px>py) swap(px,py);
     90                 ss=s.query(sx+1,sy);
     91                 sp=p.query(px+1,py);
     92                 if(ss+sp<i||sp==0) continue;
     93                 l=max(j-i-i+1,j-i-sp+1);
     94                 r=min(j+i-1,j+ss);
     95                 S[l]++,S[r-2*i+2]--;
     96                 E[l+2*i-1]++,E[r+1]--;
     97             }
     98         }
     99         for(int i=1;i<=len;i++)
    100             st[i]=st[i-1]+S[i],ed[i]=ed[i-1]+E[i];
    101         ll ans=0;
    102         for(int i=2;i<=len;i++)
    103             ans+=ed[i-1]*st[i];
    104         printf("%lld
    ",ans);
    105     }
    106     return 0;
    107 }
  • 相关阅读:
    100到简单加减乘除算法的程序
    安卓日程管理系统中的bug
    绑定到Collection与绑定到CollectionViewSource的不同及解决方案
    【WPF】完美的布局不留白——解决WrapPanel右侧留白问题
    WPF里最简单的控件的Style你能写对么?(默认Style是有问题的)
    WPF Bug清单之(13)——应该出现却没有出现的ListView水平滚动条
    [WPF Bug清单]之(12)——与自定义Attached Property相关的Binding Path运行时错误
    请争取你可以拥有的——即使你不在乎
    C#编码风格——using语句的位置
    【WPF】实现QQ中的分组面板(2)——添加动画
  • 原文地址:https://www.cnblogs.com/guapisolo/p/9707026.html
Copyright © 2011-2022 走看看