先放个简单版的
Pku2406 Power Strings
Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc" and b = "def" then a*b = "abcdef". If we think of concatenation as multiplication, exponentiation by a non-negative integer is defined in the normal way: a^0 = "" (the empty string) and a^(n+1) = a*(a^n).
求一个字符串由多少个重复的子串连接组成,例如ababab由3个ab连接而成,因此答案为3,又例如abcd由1个abcd连接而成,因此答案为1
Input
Each test case is a line of input representing s, a string of printable characters. The length of s will be at least 1 and will not exceed 1 million characters. A line containing a period follows the last test case.
Output
For each s you should print the largest n such that s = a^n for some string a.
Sample Input
abcd
aaaa
ababab
.
Sample Output
1
4
3
Sol1:Hash
#include<iostream> #include<cstdio> #include<string> #include<cstring> using namespace std; typedef unsigned long long LL; const LL base=131; const int N=1000010; int n; LL power[N],sum[N]; bool check(LL v,int k) //判断s[1]~s[k]是否是循环节 { for(register int i=1;i<=n;i+=k){ if(v!=sum[i+k-1]-sum[i-1]*power[k]) return 0; } return 1; } int main() { power[0]=1; for(register int i=1;i<=N-10;++i) //hash准备工作 power[i]=power[i-1]*base; char s[N]; while(scanf("%s",s+1)){ if(s[1]=='.')break; n=strlen(s+1); sum[0]=0; for(register int i=1;i<=n;++i) sum[i]=sum[i-1]*base+LL(s[i]); for(register int i=1;i<=n;++i){ if(n%i)continue; LL expect=sum[i]; if(check(expect,i)){ printf("%d ",n/i); break; } } } return 0; }
Sol2:Kmp算法
#include<cctype> #include<cstdio> #include<cstring> using namespace std; int nxt[1000010],m; char b[1000010]; void get_next() { memset(nxt,0,sizeof(nxt)); int j=0; for(int i=2;i<=m;i++) { while(j&&b[i]!=b[j+1])j=nxt[j]; if(b[j+1]==b[i])nxt[i]=++j; } } int main() { while(1) { scanf("%s",b+1); if(b[1]=='.')break; m=strlen(b+1); get_next(); //此题求的东西与后面的poi那个正好相反 //例如"abcabcabc",易知next[9]=6,即字符串前缀长度为6的,正好等于后缀长度为6 //如果存在循环节,则9-next[9]这个数字应该为9的约数 //这样形如s1s2s3 //等于 s1s2s3 //即s2s3这一段等于s1s2这一样,于是我们将其等长划分后 //s2=s1,s3=s2,于是s1=s2=s3 if(m%(m-nxt[m])==0) printf("%d ",m/(m-nxt[m])); else puts("1"); } }
给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节。
如果字符串B是字符串A的循环节,那么A可以由B重复若干次得到。
Input
第一行一个正整数n (n<=500,000),表示S的长度。
第二行n个小写英文字母,表示字符串S。
第三行一个正整数q (q<=2,000,000),表示询问个数。
下面q行每行两个正整数a,b (1<=a<=b<=n),表示询问字符串S[a..b]的最短循环节长度。
Output
依次输出q行正整数,第i行的正整数对应第i个询问的答案。
Sample Input
8
aaabcabc
3
1 3
3 8
4 8
Sample Output
1
3
5
Sol:设Len为所求的区间长度,然后对Len这个数字做质因子分解
例如Len=20时,20=2*2*5
首先取出长度为20/2=10的字符串的前缀及后缀,看其是否相等,如果相等则Len=10
然后取出长度为10/2=5的字符串的前缀及后缀,看是否相等,如果不等,则Len不变
然后取出Len=10/5=2的字符串的前缀及后缀,看是否相等。
这个思路其实是求字符串的循环节,可参考Period那个题(Kmp算法)
/* 列出以下性质: 1、循环节一定是长度的约数(废话。。。) 2、如果n是一个循环节,那么k*n也必定是一个循环节(关键所在) 3、n是[l,r]这一段的循环节的充要条件是 ?[l,r-n]和[l+n,r]相同 (利用这个性质我们在判断是否为循环节是可以做到O(1)) 我们从性质2开始,如果len是循环节,那么最小循环节一定是len的约数——是不是有想法了? 枚举所有的质因子,不断除以原长并保证其仍是循环节,直到不能再小为止。 复杂度O(nlogn)(常数很小) 原文链接:https://blog.csdn.net/zeyu_king/article/details/41989279 */ #include<bits/stdc++.h> #define ll long long #define N 500005 #define mod 19260817 using namespace std; inline void read(ll &x) { ll datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} x=okoko?-datta:datta; } ll p[N],n,m,q[N],l,r,hx[N],base=29; ll prime[N],pnum,len,son[N],nn; char c[N]; bool h[N]; void pre() { for(int i=2;i<=n;i++) { if(!h[i]) { prime[++pnum]=i; p[i]=i;//对于数字i所能分出的最小质因子,注意是最小的 } for(int j=1;j<=pnum&&i*prime[j]<=n;j++) { h[i*prime[j]]=true; p[i*prime[j]]=prime[j]; if(!i%prime[j])break; } } q[0]=1; for(int i=1;i<=n;i++) q[i]=(q[i-1]*base)%mod; for(int i=1;i<=n;i++) { hx[i]=hx[i-1]*base+c[i]-'a'+1; hx[i]=hx[i]%mod; } } ll fk(ll a,ll b) { ll num=hx[b]-(hx[a-1]*q[b-a+1])%mod; num=(num+mod)%mod; return num; } bool cheak(ll a,ll b,ll l) //如果[a,b]这一段字符串存在循环节,其长度为L //则充要条件为[a,b-L]这一段等于[a+L,b]这一段 //形如下图 //1 2 3 4 5 // 1 2 3 4 5 //1=2,2=3,3=4,4=5,于是它们就都相等了 { if(fk(a,b-l)==fk(a+l,b)) return true; else return false; } int main() { read(n); scanf("%s",c+1); pre(); read(m); for(int i=1;i<=m;i++){ read(l),read(r); len=r-l+1,nn=0; while(len!=1) //对数字Len进行质因子分解,这个分解写得好奇怪 { son[++nn]=p[len]; len=len/p[len]; } len=r-l+1; for(int j=1;j<=nn;j++) { ll now=len/son[j]; //将字符串分成son[j]段,每段长度为now if(cheak(l,r,now)) len=now; } printf("%lld ",len); } return 0; }