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
    */
    
  • 相关阅读:
    leetcode 48. Rotate Image
    leetcode 203. Remove Linked List Elements 、83. Remove Duplicates from Sorted List 、82. Remove Duplicates from Sorted List II(剑指offer57 删除链表中重复的结点) 、26/80. Remove Duplicates from Sorted ArrayI、II
    leetcode 263. Ugly Number 、264. Ugly Number II 、313. Super Ugly Number 、204. Count Primes
    leetcode 58. Length of Last Word
    安卓操作的一些问题解决
    leetcode 378. Kth Smallest Element in a Sorted Matrix
    android studio Gradle Build速度加快方法
    禁用gridview,listview回弹或下拉悬停
    Android Studio找不到FragmentActivity类
    安卓获取ListView、GridView等滚动的距离(高度)
  • 原文地址:https://www.cnblogs.com/suncongbo/p/10311294.html
Copyright © 2011-2022 走看看