bzoj1396 识别子串

貌似是个傻×题啊.......由于贪心地想要刷双,结果被空间卡了半个小时。

这个题是问字符串S每一位的最短识别子串是多长(识别子串指包含这个字符且只出现在S中一次的子串)。

为了想这个题,我对着SAM的图YY了一天。终于,我发现,从后缀树的角度考虑是比较简单的。因为SAM是一棵逆序的后缀树,那么每一位i的前缀(除了包含在别的前缀里面)所对应的节点一定是后缀树里的叶子节点。那么我们只要找到它的父亲节点,然后它的父亲节点对应的子串填上一位字符就是一个识别子串。

然后我们就求出了每一位往前最短的识别子串的长度(比这个长度长的一定是识别子串)。然后,我们看:

其中L~R是最短识别字串

1~L-1段,可以用长度R-i+1更新,L^R段,可以用长度R-L+1更新。于是用两个线段树维护就行了。

strspot
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<algorithm>
  4 #include<cmath>
  5 #include<cstring>
  6 #define maxn 500100
  7 #define inf 1000000000
  8 using namespace std;
  9 struct node
 10 {
 11     node *ch[27],*pre;
 12     int len;
 13     bool v;
 14 }sam[maxn*2],*rot,*now;
 15 char s[maxn];
 16 int n,m,num;
 17 struct segtree
 18 {
 19     int key[maxn*3],cov[maxn*3];
 20     void build(int x,int l,int r)
 21     {
 22         key[x]=cov[x]=inf;
 23         if (l==r) return ;
 24         int mid=(l+r)>>1;
 25         build(x*2,l,mid); build(x*2+1,mid+1,r);
 26     }
 27     void cover(int x,int z)
 28     {
 29         cov[x]=min(cov[x],z);
 30         key[x]=min(key[x],z);
 31     }
 32     void down(int x)
 33     {
 34         if (cov[x]==inf) return ;
 35         cover(x*2,cov[x]); cover(x*2+1,cov[x]);
 36         cov[x]=inf;
 37     }
 38     void add(int x,int l,int r,int ll,int rr,int z)
 39     {
 40         down(x);
 41         if (ll<=l&&r<=rr) cover(x,z);
 42         else
 43         {
 44             int mid=(l+r)>>1;
 45             if (ll<=mid) add(x*2,l,mid,ll,rr,z);
 46             if (rr>mid) add(x*2+1,mid+1,r,ll,rr,z);
 47             key[x]=min(key[x*2],key[x*2+1]);
 48         }
 49     }
 50     int ask(int x,int l,int r,int z)
 51     {
 52         down(x);
 53         if (l==r) return key[x];
 54         int mid=(l+r)>>1;
 55         if (z<=mid) return ask(x*2,l,mid,z);
 56         else return ask(x*2+1,mid+1,r,z);
 57     }
 58 }S,T;
 59 
 60 void insert(int w)
 61 {
 62     node *p=now,*np=&sam[++num];
 63     np->len=p->len+1; np->v=1;
 64     while (p&&p->ch[w]==0) p->ch[w]=np,p=p->pre;
 65     if (!p) np->pre=rot;
 66     else
 67     {
 68         node *q=p->ch[w];
 69         if (p->len+1==q->len) np->pre=q;
 70         else
 71         {
 72             node *nq=&sam[++num];
 73             *nq=*q;
 74             nq->len=p->len+1; nq->v=0;
 75             np->pre=q->pre=nq;
 76             while (p&&p->ch[w]==q) p->ch[w]=nq,p=p->pre;
 77         }
 78     }
 79     now=np;
 80 }
 81 
 82 int main()
 83 {
 84     scanf("%s",s);
 85     n=strlen(s);
 86     rot=now=&sam[num=0];
 87     for (int i=0;i<n;i++) insert(s[i]-'a');
 88     S.build(1,1,n);
 89     T.build(1,1,n);
 90     for (int i=1;i<=num;i++)
 91         sam[i].pre->v=0;
 92     for (int i=1;i<=num;i++) 
 93         if (sam[i].v==1)
 94         {
 95             node *p=&sam[i];
 96             int l=(p->len)-(p->pre->len),r=p->len;
 97             S.add(1,1,n,l,r,r-l+1);
 98             if (l>1) T.add(1,1,n,1,l-1,r);
 99         }
100     for (int i=1;i<=n;i++)
101         printf("%d\n",min(S.ask(1,1,n,i),T.ask(1,1,n,i)-i+1));
102     return 0;
103 }

其实这个题后缀数组也可以搞。

 

转载于:https://www.cnblogs.com/zig-zag/archive/2013/04/27/3047604.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值