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

嗯,以后博客内容就这样规定吧:

近期,以下三类题目做完之后必须写题解,其他的任意

数学、字符串、网络流

好了进入正题

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

题目大意:

给定长度为\(n\)的字符串\(a\), 对每一个\(i\in [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(n\log 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\n",ans);
}
return 0;
}
/*
bacaca
*/

BZOJ 1396 识别子串 (后缀自动机、线段树)的更多相关文章

  1. BZOJ 1396 识别子串 (后缀自动机+线段树)

    题目大意: 给你一个字符串S,求关于每个位置x的识别串T的最短长度,T必须满足覆盖x,且T在S中仅出现一次 神题 以节点x为结尾的识别串,必须满足它在$parent$树的子树中只有一个$endpos$ ...

  2. BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

    这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...

  3. BZOJ1396: 识别子串(后缀自动机 线段树)

    题意 题目链接 Sol 后缀自动机+线段树 还是考虑通过每个前缀的后缀更新答案,首先出现次数只有一次,说明只有\(right\)集合大小为\(1\)的状态能对答案产生影响 设其结束位置为\(t\),代 ...

  4. BZOJ 1396&&2865 识别子串[后缀自动机 线段树]

    Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如, ...

  5. BZOJ.1396.识别子串(后缀自动机/后缀数组 线段树)

    题目链接 SAM:能成为识别子串的只有那些|right|=1的节点代表的串. 设这个节点对应原串的右端点为r[i],则如果|right[i]|=1,即\(s[\ [r_i-len_i+1,r_i-le ...

  6. 【BZOJ1396】识别子串 - 后缀自动机+线段树

    题意: Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 题解: ...

  7. bzoj1396&&2865 识别子串 后缀自动机+线段树

    Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. Sample Input agoodco ...

  8. bzoj 1396: 识别子串【SAM+线段树】

    建个SAM,符合要求的串显然是|right|==1的节点多代表的串,设si[i]为right集合大小,p[i]为right最大的r点,这些都可以建出SAM后再parent树上求得 然后对弈si[i]= ...

  9. bzoj 1396/2865: 识别子串 后缀自动机+线段树

    水水的字符串题 ~ #include <map> #include <cstdio> #include <cstring> #include <algorit ...

随机推荐

  1. extjs的formpanel横向滚动条的解决办法

    关于formpanel设置autoscroll:true同时出现横向和纵向滚动条的解决办法: 不要设置autoscroll属性,或者autoscroll:false,然后设置bodyStyle : ' ...

  2. 心跳机制tcp keepalive的讨论、应用及“断网”、"断电"检测的C代码实现(Windows环境下)

    版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则会用法律维权. https://blog.csdn.net/stpeace/article/details/441 ...

  3. vim copy termi

    用vim写代码时,经常遇到这样的场景,复制多行,然后粘贴. 这样做:1. 将光标移动到要复制的文本开始的地方,按v进入可视模式.2. 将光标移动到要复制的文本的结束的地方,按y复制.此时vim会自动将 ...

  4. android 从assets和res中读取文件(转)

    1. 相关文件夹介绍      在Android项目文件夹里面,主要的资源文件是放在res文件夹里面的.assets文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像xml,java文件 ...

  5. Pie(二分)

    http://poj.org/problem?id=3122 题意:将n个圆柱体的不同口味的pie分给m个人,要求每个人分得的pie必须体积相同,且来自于一块pie(即:只分得一种口味的pie),求最 ...

  6. nodejs windows环境安装

    相信对于很多关注javascript发展的同学来说,nodejs已经不是一个陌生的词眼.有关nodejs的相关资料网上已经铺天盖地.由于它的高并发特性,造就了其特殊的应用地位. 国内目前关注最高,维护 ...

  7. python 12:list(range(...)) (转化参数列表)

    numbers = list(range(1,11)) #把范围产生的数字串转化为列表储存 print(numbers) 运行结果应该是: [1,2,3,4,5,6,7,8,9,10]

  8. 猜拳游戏项目(涉及知识点Scanner、Random、For、数组、Break、Continue等)

    package day03.d3.xunhuankongzhi; import java.util.Scanner; public class CaiQuan { public static void ...

  9. 在Winform中怎么实现图片的旋转,比如说实现仪表盘功能,看代码吧,看太不懂的欢迎问

    ; //旋转的角度 //Timer定时器 private void timer1_Tick(object sender, EventArgs e) { timer1.Enabled = false; ...

  10. 使用 CSS 追踪用户

    原文地址:Crooked Style Sheets 作者:jbtronics 除了使用 JS 追踪用户,现在有人提出了还可以使用 CSS 进行网页追踪和分析,译者认为,这种方式更为 优雅,更为 简洁, ...