Codeforces Round #246 (Div. 2) D. Prefixes and Suffixes(后缀数组orKMP)
1 second
256 megabytes
standard input
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.
The single line contains a sequence of characters s1s2...s|s| (1 ≤ |s| ≤ 105) —
string s. The string only consists of uppercase English letters.
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.
ABACABA
3
1 4
3 2
7 1
AAA
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)的更多相关文章
- Codeforces Round #246 (Div. 2) D. Prefixes and Suffixes
D. Prefixes and Suffixes You have a string s = s ...
- Codeforces Round #527 (Div. 3) C. Prefixes and Suffixes
题目链接 题意:给你一个长度n,还有2*n-2个字符串,长度相同的字符串一个数前缀一个是后缀,让你把每个串标一下是前缀还是后缀,输出任意解即可. 思路;因为不知道前缀还是后缀所以只能搜,但可以肯定的是 ...
- Codeforces Round #527 (Div. 3) C. Prefixes and Suffixes (思维,字符串)
题意:给你某个字符串的\(n-1\)个前缀和\(n-1\)个后缀,保证每个所给的前缀后缀长度从\([1,n-1]\)都有,问你所给的子串是前缀还是后缀. 题解:这题最关键的是那两个长度为\(n-1\) ...
- Codeforces Round #246 (Div. 2)
题目链接:Codeforces Round #246 (Div. 2) A:直接找满足的人数,然后整除3就是答案 B:开一个vis数组记录每一个衣服的主场和客场出现次数.然后输出的时候主场数量加上反复 ...
- Codeforces Round #587 (Div. 3) A. Prefixes
链接: https://codeforces.com/contest/1216/problem/A 题意: Nikolay got a string s of even length n, which ...
- Codeforces Round #246 (Div. 2) C. Prime Swaps(贪心,数论)
题目链接:http://codeforces.com/contest/432/problem/C 首先由题意分析出:这些数是从1到n且各不相同,所以最后结果肯定是第i位的数就是i. 采用这样一种贪心策 ...
- Codeforces Round #246 (Div. 2) B. Football Kit
题目的意思是求出每个队穿主场衣服和客场衣服的次数 每个队作为主场的次数是n-1,作为客场的次数是n-1 当每个队打主场的时候肯定穿的主场衣服 当每个队打客场时,如果客场与主场的衣服不同,则穿客场衣服 ...
- Codeforces Round #246 (Div. 2) A. Choosing Teams
给定n k以及n个人已参加的比赛数,让你判断最少还能参加k次比赛的队伍数,每对3人,每个人最多参加5次比赛 #include <iostream> using namespace std; ...
- Codeforces Round #246 (Div. 2)——D题
KMP算法,没写出来,完全不理解NEXT数组.现在理解了很多 答案都在程序中 ,不过这个思想真的很神奇, 还有毛语不好,一直没看懂题目,现在懂了, 大概是:S中前缀等于后缀,求其长度,和其在S中出现了 ...
随机推荐
- Spring Session - Spring Boot
The completed guide can be found in the boot sample application. Updating Dependencies Before you us ...
- 基于visual Studio2013解决C语言竞赛题之1074八皇后
题目 解决代码及点评 /************************************************************************/ /* ...
- 【Demo 0006】Android 组件(Activity)
本章学习要点: 1. 了解Activity基本概念; 2. 掌握Activity生命周期: 3. 掌握 Activity之间跳转:
- Mac+IPAD上使用wireshark抓包
首先先下载wireshark和Xquartz wireshark地址: http://www.wireshark.org/download.html Xquartz 地址:http://xquartz ...
- Cocos2dx 3.0 过渡篇(三十一)ValueVector和Vector不得不说的故事
本文投票地址:http://vote.blog.csdn.net/Article/Details?articleid=37834689 前天看到一个颇为纠结的选择题:有一天你遇到一个外星人,这时外星人 ...
- javascript中apply和eval结合的强大用法
eval是一个函数,可以接受一个参数,这个参数可以作为js语句被解释性的执行,利用这个特性,eval和apply结合起来,可以大大简化代码 如下例子 <a class="cl ...
- Skype无法显示登录界面
Skype升级之后突然抽风,双击运行程序之后,输入用户名和密码的窗口都没了,截图如下(本机为Windows 7 32bit版本): 卸载重新安装,也无济于事.删除注册表中的Skype的相关信息后问题依 ...
- hdu 1665 That Nice Euler Circuit(欧拉定理)
输入n个点,然后从第一个点开始,依次链接点i->点i+1,最后回到第一点(输入中的点n),求得到的图形将平面分成了多少部分. 根据欧拉定理 v_num + f_num - e_num = 2可知 ...
- LeetCode——Count and Say
The count-and-say sequence is the sequence of integers beginning as follows: 1, 11, 21, 1211, 111221 ...
- hdu 4284 Travel(floyd + TSP)
虽然题中有n<=100个点,但实际上你必须走过的点只有H<=15个.而且经过任意点但不消耗C[i]跟D[i]可以为无限次,所以可以floyd预处理出H个点的最短路,之后剩下的...就成了裸 ...