用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:字符串_1

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:jxm2001:字符串_1 [2020/08/27 10:46]
jxm2001
2020-2021:teams:legal_string:jxm2001:字符串_1 [2020/08/27 11:38] (当前版本)
jxm2001
行 21: 行 21:
 如果 $s_{i+1}\neq s_{f(i)+1}$,则只能考虑第二长的真前后缀,暂时记为 $j$。 如果 $s_{i+1}\neq s_{f(i)+1}$,则只能考虑第二长的真前后缀,暂时记为 $j$。
  
-于是有 $s[1,​j]=s[i-j+1,​i]=s[f(i)-j+1,​f(i)]$发现 $j$ 是 $s[1,f(i)]$ 的真前后缀,于是有 $j=f(f(i))$。+根据已知,有 $s[1,​j]=s[i-j+1,​i]=s[f(i)-j+1,​f(i)]$。 
 + 
 +发现 $j$ 是 $s[1,f(i)]$ 的真前后缀,于是有 $j=f(f(i))$。
  
 不停尝试,直到 $j=0$ 即没有满足条件真前后缀,可以停止尝试。 不停尝试,直到 $j=0$ 即没有满足条件真前后缀,可以停止尝试。
行 324: 行 326:
 考虑求出每个位置的 $z$ 函数,然后找到与前缀相同的在中间出现过的最长字串,有 $\text{pre}=\max_{1\lt i \lt n}(\min(z(i),​n-i))$。 考虑求出每个位置的 $z$ 函数,然后找到与前缀相同的在中间出现过的最长字串,有 $\text{pre}=\max_{1\lt i \lt n}(\min(z(i),​n-i))$。
  
-接下来从大到小枚举每个与前缀相同的后缀,判定条件为 $i+z(i)-1=n$如果该后缀长度小于 $\text{pre}$,则得到答案。时间复杂度 $O(|S|)$。+接下来从大到小枚举每个与前缀相同的后缀,判定条件为 $i+z(i)-1=n$如果该后缀长度小于 $\text{pre}$,则得到答案。时间复杂度 $O(|S|)$。
  
 <hidden 查看代码>​ <hidden 查看代码>​
行 356: 行 358:
  else  else
  _rep(i,​1,​ans)putchar(s[i]);​  _rep(i,​1,​ans)putchar(s[i]);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +== 题解 2 ==
 +
 +考虑求出每个位置的 $f$ 函数,同样找到与前缀相同的在中间出现过的最长字串,有 $\text{pre}=\max_{1\lt i \lt n}f(i)$。
 +
 +接下来从大到小枚举每个与前缀相同的后缀,初始为 $f(n)$,然后不断迭代 $f$ 即可。如果该后缀长度小于 $\text{pre}$,则得到答案。时间复杂度 $O(|S|)$。
 +
 +=== 习题二 ===
 +
 +[[https://​www.luogu.com.cn/​problem/​CF432D|CF432D]]
 +
 +== 题意 ==
 +
 +给你一个长度为 $n$ 的字符串。定义“完美子串”既是它的前缀也是它的后缀,求“完美子串”的个数且统计这些子串的在字符串中出现的次数。
 +
 +== 题解 1 ==
 +
 +考虑求出每个位置的 $z$ 函数,统计每个前缀出现次数。关于 $z$ 函数统计前缀次数,如果 $s[l,​r]=s[1,​r-l+1]$,显然有 $s[l,​r-1]=s[1,​r-l]$。
 +
 +于是可以直接用桶维护 $z$ 函数,然后求后缀和。
 +
 +然后从大到小枚举每个与前缀相同的后缀并记录答案,判定条件为 $i+z(i)-1=n$。时间复杂度 $O(|S|)$。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1e5+5;
 +namespace KMP{
 + int z[MAXN];
 + void get_z(char *s,int n){
 + z[1]=n;
 + _rep(i,​2,​n)z[i]=0;​
 + for(int i=2,​l=0,​r=0;​i<​=n;​i++){
 + if(i<​=r)z[i]=min(z[i-l+1],​r-i+1);​
 + while(i+z[i]<​=n&&​s[z[i]+1]==s[i+z[i]])z[i]++;​
 + if(i+z[i]-1>​r)l=i,​r=i+z[i]-1;​
 + }
 + }
 +}
 +int c[MAXN];
 +char s[MAXN];
 +int main()
 +{
 + gets(s+1);
 + int n=strlen(s+1);​
 + KMP::​get_z(s,​n);​
 + stack<​int>​ s;
 + _rep(i,​1,​n){
 + if(i+KMP::​z[i]-1==n)
 + s.push(KMP::​z[i]);​
 + c[KMP::​z[i]]++;​
 + }
 + for(int i=n;​i;​i--)c[i-1]+=c[i];​
 + enter(s.size());​
 + while(!s.empty()){
 + printf("​%d %d\n",​s.top(),​c[s.top()]);​
 + s.pop();
 + }
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +== 题解 2 ==
 +
 +考虑求出每个位置的 $f$ 函数,统计每个前缀出现次数。
 +
 +然后从大到小枚举每个与前缀相同的后缀并记录答案,初始为 $n$,然后不断迭代 $f$ 即可。时间复杂度 $O(|S|)$。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAXN=1e5+5;
 +namespace KMP{
 + int f[MAXN];
 + void get_f(char *s,int n){ 
 + int pos=0;
 + f[1]=0;
 + _rep(i,​2,​n){
 + while(pos&&​s[i]!=s[pos+1])pos=f[pos];​
 + if(s[i]==s[pos+1])pos++;​
 + f[i]=pos;​
 + }
 + }
 +}
 +char s[MAXN];
 +int c[MAXN];
 +int main()
 +{
 + gets(s+1);
 + int n=strlen(s+1);​
 + KMP::​get_f(s,​n);​
 + _rep(i,​1,​n)c[KMP::​f[i]]++;​
 + stack<​pair<​int,​int>​ >pt;
 + for(int i=n;​i;​i--)c[KMP::​f[i]]+=c[i];​
 + for(int i=n;​i;​i=KMP::​f[i])
 + pt.push(make_pair(i,​c[i]+1));​
 + enter(pt.size());​
 + while(!pt.empty()){
 + printf("​%d %d\n",​pt.top().first,​pt.top().second);​
 + pt.pop();
 + }
  return 0;  return 0;
 } }
 </​code>​ </​code>​
 </​hidden>​ </​hidden>​
2020-2021/teams/legal_string/jxm2001/字符串_1.1598496364.txt.gz · 最后更改: 2020/08/27 10:46 由 jxm2001