zoukankan      html  css  js  c++  java
  • BZOJ 1396 识别子串 (后缀自动机、线段树)

    手动博客搬家: 本文发表于20181221 00:58:26, 原地址https://blog.csdn.net/suncongbo/article/details/85150962

    嗯,以后博客内容就这样规定吧:
    近期,以下三类题目做完之后必须写题解,其他的任意
    数学、字符串、网络流

    好了进入正题

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1396

    题目大意:
    给定长度为(n)的字符串(a), 对每一个(iin [1,n])求包含(i)这个位置的最短的只出现一次的子串。

    题解:
    这道题目充分暴露了我SB的事实。

    讲个笑话:以下两个问题我各想了半小时都没有想出来。
    1. 如何求对于SAM中Right集合大小为1的点求出Right集合。
    2. 如何支持插入一条先斜率为-1下降再平的直线,最后求每个位置上的最小值。
    

    建出(a)串的(SAM). 求出每一个节点的(Right)集合大小即为其所代表子串的出现次数。
    对于每一个(Right)集合大小为(1)的状态,我们求出它的(Right)集合,也就是这个串具体出现在哪个位置。怎么做?很简单,直接插入SAM的时候记录每个状态在第几次被插入的即为出现位置。有性质:(Right)集合大小为(1)当且仅当这个点在(Parent)树上是叶子节点。 简单吧,这都想不出来,我真是个大SB。
    然后我们考虑它对答案的贡献:设(maxlen[i], minlen[i])分别表示(i)代表子串的最大最小长度(right[i])表示(i)的出现位置右端点,画一画可以发现,对于([right[i]-minlen[i]+1,right[i]])这段区间,我们用(minlen[i])更新每一个点原答案(这是为了保证出现次数为(1));对于([right[i]-maxlen[i]+1,right[i]-minlen[i]])这段区间中下标为(k)的点,我们用(right[i]+maxlen[i]-k)更新(k)的答案(这是为了在出现次数为(1)的基础上保证这个右端点为(right)的子串跨过(i))。
    emm, 这样说可能比较难以理解,举个例子:
    假设当前节点(right=7, maxlen=5, minlen=3)
    则它对答案的贡献是: (从左往右是(3,4,5,6,7))(5,4,3,3,3)
    对于(5,6,7)两格,当串长为(3)时该串为(a[5,7])恰好能够覆盖(5,6,7)三个点。若串长为(2)则小于(minlen), 跳到了(SAM)别的节点上,不合法。
    对于(4)格,当串长为(3)时该串为(a[5,7]), 虽然满足出现了一次,但是这个串太短不足以覆盖(4)这个点。因此扩大串长至(4), 该串为(a[4,7])
    对于(3)格同理扩大串长至(5)
    然后推一波就可以得到刚才的式子。

    好了现在我们已经把问题转化成了:每次给一个区间做上述奇奇怪怪的update, 最后询问每个点的值。
    我们先观察:“奇奇怪怪的update”, 实际上是要支持区间插入一条斜率为(-1)(0)的直线,最后求每个点上直线的最低位置。看上去最暴力的想法是李超树直接上,但是无敌的Creed_巨佬告诉我我自闭了。
    于是就只好继续想简单做法,发现可以根据斜率为(-1,0)的优美性质搞个在线线段树,最后口胡完了发现就是个整数版李超树……
    最后,不知道为啥我硬想(1h)想不出来,然后看题解,看了几个字,发现这东西巨蠢……
    维护两棵线段树,分别表示斜率为(0)(-1)的,取(min)即可
    看吧,我果然是个SB

    这个做法完美地利用了这个实际情景中的离线性质。
    然后我们就可以开心地去写两棵线段树啦。
    等等……诶代码量有点大??
    我们考虑斜率为(-1)的直线,如果我们对每一个最终的数值(a_i)作变换(a_i=a_i+i), 那么斜率为(-1)的直线就变成了斜率为(0)的!继续用刚才方法维护!
    一遍线段树,做完了。(SB退役选手终于自己想出了这道题的一部分)
    另外,第一次写标记永久化线段树。在这道题里为了保证常数,个人认为标记永久化会快一些。反正怎么做都可以。
    时间复杂度(O(nlog n)).

    代码实现

    //Wrong Coding:
    //Forget to return in mdfmin()
    //lb = rb-... -> lb = lb-...
    //SGT Range is sam.siz<<2 not n<<2
    //segtree1.query()... not segtree2
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #define llong long long
    using namespace std;
    const int N = 2e5;
    const int S = 26;
    const int INF = 1e7;
    char a[N+3];
    int n;
    struct SAM
    {
     int len[N+3];
     int fa[N+3];
     int son[N+3][S+3];
     int buc[N+3];
     int oid[N+3];
     int rid[N+3];
     int sz[N+3];
     int siz,lstpos,rtn;
     void init()
     {
      rtn = siz = lstpos = 1;
     }
     void insertstr(char ch,int id)
     {
      int p = lstpos,np; siz++; lstpos = np = siz; len[np] = len[p]+1; sz[np] = 1; rid[np] = id;
      for(; p && son[p][ch]==0; p=fa[p]) {son[p][ch] = np;}
      if(p==0) {fa[np] = rtn;}
      else
      {
       int q = son[p][ch];
       if(len[q]==len[p]+1) {fa[np] = q;}
       else
       {
        siz++; int nq = siz; len[nq] = len[p]+1;
        memcpy(son[nq],son[q],sizeof(son[q]));
        fa[nq] = fa[q]; fa[np] = fa[q] = nq;
        for(; p && son[p][ch]==q; p=fa[p]) {son[p][ch] = nq;}
       }
      }
     }
     void sortnode()
     {
      for(int i=1; i<=siz; i++) buc[len[i]]++;
      for(int i=1; i<=siz; i++) buc[i] += buc[i-1];
      for(int i=siz; i>=1; i--) oid[buc[len[i]]--] = i;
      for(int i=siz; i>=1; i--) {int u = oid[i]; if(fa[u]) sz[fa[u]]+=sz[u];}
     }
    } sam;
    struct SegmentTree
    {
     struct SgTNode
     {
      int tag;
     } sgt[(N<<2)+2];
     void mdfmin(int pos,int le,int ri,int lb,int rb,int val)
     {
      if(lb>rb) return;
      if(le==lb && ri==rb) {sgt[pos].tag = min(sgt[pos].tag,val); return;}
      int mid = (le+ri)>>1;
      if(rb<=mid) mdfmin(pos<<1,le,mid,lb,rb,val);
      else if(lb>mid) mdfmin(pos<<1|1,mid+1,ri,lb,rb,val);
      else {mdfmin(pos<<1,le,mid,lb,mid,val); mdfmin(pos<<1|1,mid+1,ri,mid+1,rb,val);}
     }
     int queryval(int pos,int le,int ri,int lrb)
     {
      if(le==ri) {return sgt[pos].tag;}
      int mid = (le+ri)>>1;
      if(lrb<=mid) return min(queryval(pos<<1,le,mid,lrb),sgt[pos].tag);
      else return min(queryval(pos<<1|1,mid+1,ri,lrb),sgt[pos].tag);
     }
    } segtree1,segtree2;
    int main()
    {
     scanf("%s",a+1); n = strlen(a+1);
     sam.init();
     for(int i=1; i<=n; i++) sam.insertstr(a[i]-96,i);
     for(int i=0; i<=(sam.siz<<2); i++) segtree1.sgt[i].tag = segtree2.sgt[i].tag = INF;
     sam.sortnode();
     for(int i=sam.siz; i>=1; i--)
     {
      if(sam.sz[i]==1)
      {
       int minlen = sam.len[sam.fa[i]]+1,maxlen = sam.len[i];
       int rb = sam.rid[i],lb = rb-minlen+1;
       segtree2.mdfmin(1,1,n,lb,rb,minlen);
       rb = lb-1; lb = lb-(maxlen-minlen);
       segtree1.mdfmin(1,1,n,lb,rb,maxlen+lb);
      }
     }
     for(int i=1; i<=n; i++)
     {
      int ans1 = segtree1.queryval(1,1,n,i)-i,ans2 = segtree2.queryval(1,1,n,i);
      int ans = min(ans1,ans2);
      printf("%d
    ",ans);
     }
     return 0;
    }
    /*
    bacaca
    */
    
  • 相关阅读:
    elasticsearch ——id字段说明,内部是_uid
    企业安全建设之搭建开源SIEM平台(上)
    江西鹰潭、江西移动与华为战略合作:共推物联网——物联网的世界要到来了
    Luke 5—— 可视化 Lucene 索引查看工具,可以查看ES的索引
    Apache Flink vs Apache Spark——感觉二者是互相抄袭啊 看谁的好就抄过来 Flink支持在runtime中的有环数据流,这样表示机器学习算法更有效而且更有效率
    转:shell比较两个字符串是否相等
    UNIX 缩写风格
    转:.Net程序员学习Linux最简单的方法
    asp.net插入sql server 中文乱码问题解决方案
    asp.net将object或string转为int
  • 原文地址:https://www.cnblogs.com/suncongbo/p/10311294.html
Copyright © 2011-2022 走看看