D. Prefixes and Suffixes
time limit per test

1 second

memory limit per test

256 megabytes

input

standard input

output

standard output

You have a string s = s1s2...s|s|,
where |s| is the length of string s,
and si its i-th
character.

Let's introduce several definitions:

  • A substring s[i..j] (1 ≤ i ≤ j ≤ |s|) of
    string s is string sisi + 1...sj.
  • The prefix of string s of length l (1 ≤ l ≤ |s|) is
    string s[1..l].
  • The suffix of string s of length l (1 ≤ l ≤ |s|) is
    string s[|s| - l + 1..|s|].

Your task is, for any prefix of string s which matches a suffix of string s,
print the number of times it occurs in strings as a substring.

Input

The single line contains a sequence of characters s1s2...s|s| (1 ≤ |s| ≤ 105) —
string s. The string only consists of uppercase English letters.

Output

In the first line, print integer k (0 ≤ k ≤ |s|) —
the number of prefixes that match a suffix of string s. Next print klines,
in each line print two integers li ci.
Numbers li ci mean
that the prefix of the length li matches
the suffix of length li and
occurs in string s as a substring ci times.
Print pairs li ci in
the order of increasing li.

Sample test(s)
input
ABACABA
output
3
1 4
3 2
7 1
input
AAA
output
3
1 3
2 2
3 1

题意:

给你一个长度不超过10^5的字符串。要你按长度输出和后缀全然匹配的的前缀的长度。和该前缀在整个串中出现的次数。(可重叠)

思路:

比赛时一看到前缀后缀。心里一阵窃喜。

哈哈。刚好学过后缀数组。正好实用武仅仅地了,一番思索后算法已成型。0号后缀就是整个字符串。

和它求公共前缀能和整个后缀匹配的后缀一定有一个前缀能和这个后缀全然匹配。 然后再确定出现了多少次。 当你知道某个后缀是目标后缀时。 你能够知道到它的rank值。 然后要全然包括一个后缀的后缀一定在它后面。

依据排名规则。 你想啊。假设后缀a的前缀包括后缀b。a还会排在b前面吗? 明显长度短的排前面了。 所以剩下工作就是确定能够向下扩展的最大距离了。
这个能够依据height数据的值确定。 要用到二分+rmq。二分确定位置。

rmq推断是否满足条件。思路尽管正确可是到比赛结束都一直是错的。到后面调试出来才知道还是对后缀数组的理解不够深刻。问题就出在倍增算法为什么要规定txt[n-1]=0.还有j=sa[rank[i]-1];rank[i]=0怎么处理。我们把原来的字符串末尾加个0就可解决。具体见代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double PI=acos(-1.0);
const int maxn=150010;
char txt[maxn];
int sa[maxn],T1[maxn],T2[maxn],ct[maxn],he[maxn],rk[maxn],ans,n,m;//sa[i]表示排名第i的后缀的起始位置。
int rmq[25][maxn],lg[maxn],ansn[maxn],ansp[maxn],ptr;
void getsa(char *st)//注意m为ASCII码的范围
{
int i,k,p,*x=T1,*y=T2;
for(i=0; i<m; i++) ct[i]=0;
for(i=0; i<n; i++) ct[x[i]=st[i]]++;
for(i=1; i<m; i++) ct[i]+=ct[i-1];
for(i=n-1; i>=0; i--)//倒着枚举保证相对顺序
sa[--ct[x[i]]]=i;
for(k=1,p=1; p<n; k<<=1,m=p)//枚举长度
{
for(p=0,i=n-k; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=k) y[p++]=sa[i]-k;//按第二keyword排序.y[i]表示第二keyword排名第i的后缀起始位置
for(i=0; i<m; i++) ct[i]=0;
for(i=0; i<n; i++) ct[x[y[i]]]++;//x[i]表示起始位置为i的后缀的第一keyword排序
for(i=1; i<m; i++) ct[i]+=ct[i-1];
for(i=n-1; i>=0; i--) sa[--ct[x[y[i]]]]=y[i];//接着按第一keyword排序
for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++)
x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;//x[i]存排名第i后缀的排名
//保证txt[n-1]=0.规定txt[n-1]=0 的优点,假设y[sa[i-1]]=y[sa[i]]。说明以y[sa[i-1]]或y[sa[i]]
//开头的长度为k的字符串肯定不包含字符txt[n-1]由于包含了肯定就不相等了。所以调用变量y[sa[i]+l]和y[sa[i-1]+l]
//不会导致数组下标越界,这样就不须要做特殊推断。
}
}
void gethe(char *st)//求height数组
{
int i,j,k=0;
for(i=0;i<n;i++) rk[sa[i]]=i;
for(i=0;i<n-1;i++)
{
if(k) k--;
j=sa[rk[i]-1];
while(st[i+k]==st[j+k]) k++;
he[rk[i]]=k;
}
}
void rmq_init()
{
int i,j;
for(i=0;i<n;i++)
rmq[0][i]=he[i];//单个元素
for(i=1;i<=lg[n];i++)//枚举长度
for(j=0;j+(1<<i)-1<n;j++)//枚举起点注意边界
rmq[i][j]=min(rmq[i-1][j],rmq[i-1][j+(1<<(i-1))]);
}
int rmq_min(int l,int r)
{
int tmp=lg[r-l+1];
return min(rmq[tmp][l],rmq[tmp][r-(1<<tmp)+1]);
}
void prermq()
{
int i;
lg[0]=-1;
for(i=1;i<maxn;i++)
lg[i]=lg[i>>1]+1;
}
void solve()
{
int low,hi,mid,p,pos,a,b,ans,tp,i;
getsa(txt),gethe(txt),rmq_init();
ptr=0,pos=rk[0];
for(i=n-2;i>0;i--)
{
if(rk[i]<pos)
a=rk[i]+1,b=pos;
else
a=pos+1,b=rk[i];
p=rmq_min(a,b); if(p>=n-i-1)
{
ansp[ptr]=p,tp=rk[i]+1;
low=rk[i]+1,hi=n-1,ans=-1;
while(low<=hi)
{
mid=(low+hi)>>1;
if(rmq_min(tp,mid)>=p)
ans=mid,low=mid+1;
else
hi=mid-1;
}
ansn[ptr++]=ans-rk[i]+1;
}
}
}
int main()
{
int i; prermq();
while(~scanf("%s",txt))
{
m=150,n=strlen(txt);
n++;
solve();
ansp[ptr]=n-1;
ansn[ptr++]=1;
printf("%d\n",ptr);
for(i=0;i<ptr;i++)
printf("%d %d\n",ansp[i],ansn[i]);
}
return 0;
}

接下来是另外一种思路。

比赛完后。第一个思路调不出来。于是就去群里问了下。结果被叉姐歧视了。扔了句kmp就走了。

定神一想是啊。自己智商被深深得歧视了。kmp能够非常轻松得统计出每一个前缀在原串中出现的次数。详细做法是对原串求个失配数组。

然后自己和自己匹配。若第i个位置和第j个位置匹配了说明前缀j在第i个位置出现了一次。我们用cnt[i]记录。前缀i出现的次数。最后统计cnt[next[i]]+=cnt[i]。这个非常好理解。

假设前缀j能在i这个位置出现一次那么next[j]一定能在i这个位置出现一次。

统计完每一个前缀在原串中出现次数后。如今就要找钱缀和后缀匹配的前缀的个数了。

这个非常easy。

自己和自己匹配不就是拿自己的前半部分和自己的其它部分匹配么。所以我们仅仅须要匹配第n+1个位置就能够找出全部和后缀匹配的前缀了。华丽的O(n)就过掉了。。。

具体见代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double PI=acos(-1.0);
const int maxn=150010;
char txt[maxn];
int f[maxn],cnt[maxn],ansp[maxn],ansn[maxn],ct,n;
void getf(char *p)
{
int i,j;
f[0]=f[1]=0;
for(i=1;i<n;i++)
{
j=f[i];
while(j&&p[j]!=p[i])
j=f[j];
f[i+1]=p[j]==p[i]? j+1:0;
}
}
void KMP()
{
int i,j,t;
for(i=0,j=0;i<n;i++)
{
while(j&&txt[j]!=txt[i])
j=f[j];
if(txt[j]==txt[i])
cnt[j]++,j++;//cnt[j]表示前缀j出现次数。由于i不同所以终点不同。
}
t=j,ct=0;;
for(j=n;j>0;j--)//为什么能够这样做呢。终点不同的串一定是不同的串。 kmp保证了终点不同。 if(f[j])//f[j]表示下个比較的位置。说明前f[j]-1一定是同样的。
cnt[f[j]-1]+=cnt[j-1];
while(t)//前缀匹配后缀
{
ansp[ct]=t;
ansn[ct++]=cnt[t-1];
t=f[t];
}
printf("%d\n",ct);
for(i=ct-1;i>=0;i--)
printf("%d %d\n",ansp[i],ansn[i]);
}
int main()
{
while(~scanf("%s",txt))
{
n=strlen(txt);
memset(cnt,0,sizeof cnt);
getf(txt);
KMP();
}
}

Codeforces Round #246 (Div. 2) D. Prefixes and Suffixes(后缀数组orKMP)的更多相关文章

  1. Codeforces Round #246 (Div. 2) D. Prefixes and Suffixes

                                                        D. Prefixes and Suffixes You have a string s = s ...

  2. Codeforces Round #527 (Div. 3) C. Prefixes and Suffixes

    题目链接 题意:给你一个长度n,还有2*n-2个字符串,长度相同的字符串一个数前缀一个是后缀,让你把每个串标一下是前缀还是后缀,输出任意解即可. 思路;因为不知道前缀还是后缀所以只能搜,但可以肯定的是 ...

  3. Codeforces Round #527 (Div. 3) C. Prefixes and Suffixes (思维,字符串)

    题意:给你某个字符串的\(n-1\)个前缀和\(n-1\)个后缀,保证每个所给的前缀后缀长度从\([1,n-1]\)都有,问你所给的子串是前缀还是后缀. 题解:这题最关键的是那两个长度为\(n-1\) ...

  4. Codeforces Round #246 (Div. 2)

    题目链接:Codeforces Round #246 (Div. 2) A:直接找满足的人数,然后整除3就是答案 B:开一个vis数组记录每一个衣服的主场和客场出现次数.然后输出的时候主场数量加上反复 ...

  5. Codeforces Round #587 (Div. 3) A. Prefixes

    链接: https://codeforces.com/contest/1216/problem/A 题意: Nikolay got a string s of even length n, which ...

  6. Codeforces Round #246 (Div. 2) C. Prime Swaps(贪心,数论)

    题目链接:http://codeforces.com/contest/432/problem/C 首先由题意分析出:这些数是从1到n且各不相同,所以最后结果肯定是第i位的数就是i. 采用这样一种贪心策 ...

  7. Codeforces Round #246 (Div. 2) B. Football Kit

    题目的意思是求出每个队穿主场衣服和客场衣服的次数 每个队作为主场的次数是n-1,作为客场的次数是n-1 当每个队打主场的时候肯定穿的主场衣服 当每个队打客场时,如果客场与主场的衣服不同,则穿客场衣服 ...

  8. Codeforces Round #246 (Div. 2) A. Choosing Teams

    给定n k以及n个人已参加的比赛数,让你判断最少还能参加k次比赛的队伍数,每对3人,每个人最多参加5次比赛 #include <iostream> using namespace std; ...

  9. Codeforces Round #246 (Div. 2)——D题

    KMP算法,没写出来,完全不理解NEXT数组.现在理解了很多 答案都在程序中 ,不过这个思想真的很神奇, 还有毛语不好,一直没看懂题目,现在懂了, 大概是:S中前缀等于后缀,求其长度,和其在S中出现了 ...

随机推荐

  1. Qt之文件操作 QFile

    原地址:http://blog.csdn.net/liuhongwei123888/article/details/6084761 今天学习QT的文件操作 1.QIODevice直接继承自QObjec ...

  2. httpClient中的三种超时设置小结

    httpClient中的三种超时设置小结   本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结,希望此教程能给各位朋友带来帮助. ConnectTimeoutExceptio ...

  3. 基于visual Studio2013解决C语言竞赛题之1051数的顺序

       题目 解决代码及点评 /* 功能:自然数N一般写成如下形式: N=d[k]d[k-1]d[1] (d[1]-d[k] 均是十进制数字) 如果d[i+1]>d[i] (i=k-1 ...

  4. MFC 关于对话框的注意点

    1.对于模态对话框而言,单击确定以后对话框窗口对象即被销毁了,而对于非模态对话框来说,对话框的对象并未销毁而是隐藏起来(EndDialog函数),因此对于非模态对话框,必须重写OnOK这个虚函数,并在 ...

  5. html5的自定义data-*属性和jquery的data()方法的使用

    人们总喜欢往HTML标签上添加自定义属性来存储和操作数据.但这样做的问题是,你不知道将来会不会有其它脚本把你的自定义属性给重置掉,此外,你这样做也会导致html语法上不符合Html规范,以及一些其它副 ...

  6. Mac下MAMP初试体验

    原创文章,转载请注明出处! 近期小学习了一下Mac下的Apache,Mysql,php.这里记录一下,以备忘 1 php 1.1 php返回值的測试 在MAMP下測试成功,直接echo返回所数据 1. ...

  7. 使用OVS

    创建节点: 需要在组件上引用OVS组件WDR_OVS:引用后需要更改添加节点属性值如下: 布局如下: 在view中也添加ovs组件 新建事件方法: ON_EVENT 代码如下: METHOD on_e ...

  8. Servlet的学习之Session(3)

    在上一篇<Servlet的学习之Session(2)>我们知道了Session能实现一个会话过程中保存数据或者多个会话中实现同一个Session的关键因素就是Cookie,只是Cookie ...

  9. Oracle安装配置流程

    Oracle安装流程 第一次自己动手安装oracle,之前对oracle安装配置一窍不通,最后最终弄好.总结下. 1.  安装oracle10gserver端 2.  安装oracle10gclien ...

  10. android Activity切换动画效果

    为Activity设置左右进出的效果,能够通过在Mainfest.xml文件里设置主题的方式来实现.还能够使用java代码. 一.设置样式 先看看实现动画的样式: <style name=&qu ...