hdu 4333"Revolving Digits"(KMP求字符串最小循环节+拓展KMP)
题意:
此题意很好理解,便不在此赘述;
题解:
解题思路:KMP求字符串最小循环节+拓展KMP
①首先,根据KMP求字符串最小循环节的算法求出字符串s的最小循环节的长度,记为 k;
②根据拓展KMP求出字符串s的nex[]数组,那么对于由第 i 位打头构成的新数b,如何判断其与原数a的大小关系呢?
1)如果 i%k == 0,那么b == a;
2)如果 i%k ≠ 0 ,令L=nex[i],那么只需判断s[ i+L ]与s[ L ]的大小关系即可,需要注意的是,如果i+L = len呢?此时又该怎样处理呢?
方法1:依次判断s[0,1,....] 与 s[ L,L+1,..... ]的关系,直到找出第一个不相等的位置判断其大小;
方法2:判断 s[ nex[L] ]与s[ L+nex[L] ]的大小关系;
如果采用方法1,很不幸,会超时,所以,方法2才是行之有效的方法;
根据题意,此题让求得是不同的数,那么,如何去重呢?
根据KMP已经求出了k,那么串s得循环周期为 len / k ,那么每种新数必然会重复 len / k次,只要在输出结果上将求出的答案除以 (len/k) 即可;
还有一点需要注意的是,和原数相同的数,当且仅当只有一个,不论输入任何数,输出1即可;
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int maxn=1e6+50; 6 7 char digit[maxn]; 8 int nex[maxn]; 9 10 int Period() 11 { 12 int len=strlen(digit); 13 nex[0]=-1; 14 nex[1]=0; 15 int cnt=0; 16 int index=2; 17 while(index <= len) 18 { 19 if(digit[index-1] == digit[cnt]) 20 nex[index++]=++cnt; 21 else if(cnt != 0) 22 cnt=nex[cnt]; 23 else 24 nex[index++]=0; 25 } 26 int k=len; 27 if(len%(len-nex[len]) == 0 && nex[len] != 0) 28 k=len-nex[len]; 29 return k; 30 } 31 void getNext() 32 { 33 int len=strlen(digit); 34 nex[0]=len; 35 int j=0; 36 while(j+1 < len && digit[j+1] == digit[j]) 37 j++; 38 nex[1]=j; 39 int k=1; 40 for(int i=2;i < len;++i) 41 { 42 int p=k+nex[k]-1; 43 int l=nex[i-k]; 44 if(l < p-i+1) 45 nex[i]=l; 46 else 47 { 48 j=max(0,p-i+1); 49 while(i+j < len && digit[i+j] == digit[j]) 50 j++; 51 k=i; 52 nex[i]=j; 53 } 54 } 55 } 56 bool isLess(int i,int j,int len) 57 { 58 if(j == len)//如果j == len 59 { 60 j=nex[i]; 61 i=i+nex[i]; 62 } 63 return (digit[j]-'0') < (digit[i]-'0'); 64 } 65 void Solve() 66 { 67 int k=Period();//KMP求出最小循环节的长度 68 getNext();//拓展KMP求解nex[] 69 70 int ansL=0; 71 int ansG=0; 72 int len=strlen(digit); 73 for(int i=1;i < len;++i) 74 { 75 int l=nex[i]; 76 if(i%k == 0)//与原数相等 77 continue; 78 79 if(isLess(l,i+l,len))//判断是否小于原数 80 ansL++; 81 else 82 ansG++; 83 } 84 printf(" %d %d %d\n",ansL/(len/k),1,ansG/(len/k)); 85 } 86 int main() 87 { 88 int test; 89 scanf("%d",&test); 90 for(int kase=1;kase <= test;++kase) 91 { 92 scanf("%s",digit); 93 printf("Case %d:",kase); 94 Solve(); 95 } 96 return 0; 97 }
在网上看的其他人写的题解,有个很巧妙的方法:
将字符串s拷贝一份加入到字符串s中,通过拓展KMP求出nex[]后,对于由第 i 打头构成的新数b:
1)如果nex[i] > len/2,那么b == a;
2)判断s[ i+nex[i] ]与s[ nex[i] ]的相对大小;
分割线:2019.5.7
省赛临近,重新温习了一下拓展KMP
思路:
定义串 s , t
读入数据到 t 串,然后,复制两边 t 串到 s 串;
以 s 为母串,t 为子串求解ext[]数组;
遍历一遍串t,对于位置 i ,判断 t[ ext[i] ] 与 s[ i+ext[i] ] 的大小关系;
前者大,L++,反之, G++;
输出结果 L / k , 1 , G / k;
k : 串 t 的循环节;
之所以 / k 是因为可能有重复的;
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int maxn=2e5+50; 6 7 char s[maxn]; 8 char t[maxn]; 9 10 struct KMP 11 { 12 int nex[maxn];///数组大小根据题意而定 13 void getNex(const char *s) 14 { 15 int len=strlen(s); 16 nex[0]=-1; 17 nex[1]=0; 18 int cnt=0; 19 int index=2; 20 while(index <= len) 21 { 22 if(s[index-1] == s[cnt]) 23 nex[index++]=++cnt; 24 else if(cnt != 0) 25 cnt=nex[cnt]; 26 else 27 nex[index++]=0; 28 } 29 } 30 int F(const char *s)///返回串s的循环节 31 { 32 getNex(s); 33 int len=strlen(s); 34 int res=1; 35 if(nex[len] != 0 && len%(len-nex[len]) == 0) 36 res=len/(len-nex[len]); 37 return res;///最小循环节长度 = len/res 38 } 39 }_kmp; 40 /** 41 拓展KMP 42 nex[i]:t[0,...m-1]与t[i,...,m-1]的最长公共前缀 43 ext[i]:s[i,...n-1]与t[0,...,m-1]的最长公共前缀 44 */ 45 struct ExtendKMP 46 { 47 int nex[maxn]; 48 int ext[maxn]; 49 void getNex(const char *t)///预处理出t串的nex 50 { 51 int len=strlen(t); 52 nex[0]=len; 53 nex[1]=0; 54 for(int i=1;i < len && t[i] == t[nex[1]];i++,nex[1]++); 55 int K=1; 56 for(int i=2;i < len;++i) 57 { 58 int L=nex[i-K]; 59 nex[i]=min(L,max(K+nex[K]-i,0));///K+nex[K]-i 可能小于0,所以两者取max,整体取min 60 for(int j=i+nex[i];j < len && t[j] == t[nex[i]];j++,nex[i]++); 61 if(K+nex[K] < i+nex[i]) 62 K=i; 63 } 64 } 65 void getExtend(const char *s,const char *t) 66 { 67 int n=strlen(s); 68 int m=strlen(t); 69 ext[0]=0; 70 for(int i=0;i < n && i < m && s[i] == t[i];i++,ext[0]++); 71 int K=0; 72 for(int i=1;i < n;++i) 73 { 74 /** 75 P=K+ext[K]-1,最右边界 76 s[K,...,P] = t[0,.....,P-K] 77 s[i,...,P] = t[i-K,...,P-K] 78 t[i-K,....,i-K+L-1] = t[0,.......L-1] 79 */ 80 int L=nex[i-K]; 81 ext[i]=min(L,max(K+ext[K]-i,0)); 82 for(int j=i+ext[i];j < n && ext[i] < m && s[j] == t[ext[i]];j++,ext[i]++); 83 if(K+ext[K] < i+ext[i]) 84 K=i; 85 } 86 } 87 }_eKMP; 88 89 void Solve() 90 { 91 _eKMP.getNex(t); 92 _eKMP.getExtend(s,t); 93 94 int L=0,G=0; 95 int len=strlen(t); 96 for(int i=1;i < len;++i) 97 { 98 int ext=_eKMP.ext[i]; 99 if(ext == len) 100 continue; 101 if(s[i+ext] > t[ext]) 102 G++; 103 else 104 L++; 105 } 106 int k=_kmp.F(t);///串t的循环节 107 printf("%d 1 %d\n",L/k,G/k); 108 } 109 int main() 110 { 111 int test; 112 scanf("%d",&test); 113 for(int kase=1;kase <= test;++kase) 114 { 115 scanf("%s",t); 116 strcpy(s,t); 117 strcat(s,t);///拷贝两边t串到s 118 s[strlen(t)<<1]='\0';///可加可不加,最好加上 119 printf("Case %d: ",kase); 120 Solve(); 121 } 122 return 0; 123 }