HDU 5442 Favorite Donut(暴力 or 后缀数组 or 最大表示法)
http://acm.hdu.edu.cn/showproblem.php?pid=5442
题意:
给出一串字符串,它是循环的,现在要选定一个起点,使得该字符串字典序最大(顺时针和逆时针均可),如果有多个字典序相同的,则输出下标最小的,如果下标也是相同的,则输出顺时针方向的。
思路:
用了三种方法:
①最简单的,暴力枚举所有情况,需要优化一下,先处理出字符串中字典序最大的单词,然后接下来的起点肯定是这个单词,否则就可以跳过这个起点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 7 int n,mx; 8 string s, best; 9 10 int main() 11 { 12 // freopen("in.txt","r",stdin); 13 int T; 14 scanf("%d",&T); 15 while(T--) 16 { 17 scanf("%d",&n); 18 cin>>s; 19 mx = -1; 20 for(int i=0;i<n;i++) 21 if(s[i]-'a' > mx) mx = s[i]-'a'; 22 s += s; 23 24 int pos, dir; 25 best = ""; 26 for(int i=0;i<n;i++) 27 { 28 if(s[i]-'a'!=mx) continue; 29 string tmp = s.substr(i,n); 30 if(tmp > best) 31 { 32 pos = i; 33 dir = 0; 34 best = tmp; 35 } 36 } 37 reverse(s.begin(),s.end()); 38 for(int i=n-1;i>=0;i--) 39 { 40 if(s[i]-'a'!=mx) continue; 41 string tmp = s.substr(i,n); 42 if(tmp > best) 43 { 44 pos = n-1-i; 45 dir = 1; 46 best = tmp; 47 } 48 else if(tmp == best) 49 { 50 if(n-1-i<pos) 51 { 52 pos = n-1-i; 53 dir = 1; 54 } 55 } 56 } 57 printf("%d %d\n",pos+1,dir); 58 } 59 return 0; 60 }
②后缀数组:
正序复制一遍,求一遍后缀数组,那么sa数组中排名最后的肯定是字典序最大的,此时直接取该值即可。而且此时它一定是最小坐标。
但是求逆序的时候需要注意一下,此时就不能求最小坐标了,因为逆序了,所以最小坐标在原来的字符串中是最大的,所以此时就要求最大字典序情况下的最小坐标。那么此时就要利用height数组了,从底向上依次遍历sa数组,如果和上一个的公共前缀是>=n的话,此时字典序是相同的,但是上一个字符串的坐标更大。知道height值<n。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 const int maxn=40000+5; 7 8 int n; 9 char s[maxn]; 10 char s1[maxn],s2[maxn]; 11 int sa[maxn],t[maxn],t2[maxn],c[maxn]; 12 int Rank[maxn],height[maxn]; 13 14 void build_sa(int m) 15 { 16 int *x=t,*y=t2; 17 //基数排序 18 for(int i=0;i<m;i++) c[i]=0; 19 for(int i=0;i<n;i++) c[x[i]=s[i]]++; 20 for(int i=1;i<m;i++) c[i]+=c[i-1]; 21 for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; 22 for(int k=1;k<=n;k<<=1) 23 { 24 int p=0; 25 //直接利用sa数组排序第二关键字 26 for(int i=n-k;i<n;i++) y[p++]=i; 27 for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; 28 //基数排序第一关键字 29 for(int i=0;i<m;i++) c[i]=0; 30 for(int i=0;i<n;i++) c[x[y[i]]]++; 31 for(int i=1;i<m;i++) c[i]+=c[i-1]; 32 for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; 33 //根据sa和y计算新的x数组 34 swap(x,y); 35 p=1; 36 x[sa[0]]=0; 37 for(int i=1;i<n;i++) 38 x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++; 39 if(p>=n) 40 break; 41 m=p; //下次基数排序的最大值 42 } 43 } 44 45 void getHeight(int n) 46 { 47 int i,j,k=0; 48 for(i=1;i<=n;i++) Rank[sa[i]]=i; 49 for(i=0;i<n;i++) 50 { 51 if(k) k--; 52 int j=sa[Rank[i]-1]; 53 while(s[i+k]==s[j+k]) k++; 54 height[Rank[i]]=k; 55 } 56 } 57 58 int main() 59 { 60 //freopen("in.txt","r",stdin); 61 int T; 62 scanf("%d",&T); 63 while(T--) 64 { 65 scanf("%d",&n); 66 scanf("%s",s); 67 for(int i=0;i<n;i++) s[i+n] = s[i]; 68 s[2*n] = '0'; 69 int tmp = n; 70 n = 2*n+1; 71 build_sa(128); 72 getHeight(n-1); 73 n = tmp; 74 75 int pos1; 76 memset(s1,0,sizeof s1); 77 for(int i=2*n;i>=1;i--) 78 { 79 if(sa[i]<n) 80 { 81 pos1 = sa[i]; 82 break; 83 } 84 } 85 strncpy(s1,s+pos1,n); 86 87 reverse(s,s+2*n); 88 tmp = n; 89 n = 2*n+1; 90 build_sa(128); 91 getHeight(n-1); 92 n = tmp; 93 94 int pos2; 95 memset(s2,0,sizeof s2); 96 for(int i=2*n;i>=1;i--) 97 { 98 if(sa[i]<n) 99 { 100 while(height[i]>n) i--; 101 pos2 = n-1-sa[i]; 102 break; 103 } 104 } 105 strncpy(s2,s+n-1-pos2,n); 106 107 if(strcmp(s1,s2)>0) printf("%d 0\n",pos1+1); 108 else if(strcmp(s1,s2)<0) printf("%d 1\n",pos2+1); 109 else 110 { 111 if(pos1<=pos2) printf("%d 0\n",pos1+1); 112 else printf("%d 1\n",pos2+1); 113 } 114 } 115 return 0; 116 }
③最大表示法:
参考论文:https://wenku.baidu.com/view/c6c5e7335a8102d276a22fa6.html
算法的具体思路:
(1)开始时,将字符串复制两份,设置两个指针,i=0,j=1.
(2)k=0,然后反复迭代直到s[i+k]!=s[j+k]
(3)如果k=n,那么返回较小的值,否则看情况滑动指针
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int maxn = 40000+5; 7 8 int n; 9 char s[maxn]; 10 char s1[maxn],s2[maxn]; 11 12 int solve1(char *x) //最大表示法坐标最小 13 { 14 int i = 0, j = 1, k = 0; 15 while(i<n && j<n && k<n) 16 { 17 int t = x[i+k] - x[j+k]; 18 if(!t) k++; 19 else 20 { 21 if(t>0) j+=k+1; 22 else i+=k+1; 23 if(i==j) j++; 24 k=0; 25 } 26 } 27 return i<j?i:j; 28 } 29 30 int solve2(char *x) //最大表示法且坐标最大 31 { 32 int i = 0, j = 1, k; 33 while(i < n && j < n) 34 { 35 while(x[i+k] == x[j+k] && k < n) k++; 36 if (k == n) //这个意思就是以i开头的和以j开头的字符串相同 37 { 38 int len = abs(i - j); //len的长度就是循环节 39 return n - len + i; //取坐标最大的 40 } 41 else 42 { 43 int t = x[i+k] - x[j+k]; 44 if(t>0) j+=k+1; 45 else i+=k+1; 46 if(i==j) j++; 47 k = 0; 48 } 49 } 50 if(j >= n) return i; 51 return j; 52 } 53 54 int main() 55 { 56 //freopen("in.txt","r",stdin); 57 int T; 58 scanf("%d",&T); 59 while(T--) 60 { 61 scanf("%d",&n); 62 scanf("%s",s); 63 for(int i=0;i<n;i++) s[i+n] = s[i]; 64 s[2*n] = '\0'; 65 66 int pos1 = solve1(s); 67 memset(s1,0,sizeof s1); 68 strncpy(s1,s+pos1,n); 69 70 reverse(s,s+2*n); 71 int pos2 = solve2(s); 72 memset(s2,0,sizeof s2); 73 strncpy(s2,s+pos2,n); 74 75 int t = strcmp(s1,s2); 76 if(t>0) printf("%d 0\n",pos1+1); 77 else if(t<0) printf("%d 1\n",n-pos2); 78 else 79 { 80 if(pos1+1<=n-pos2) printf("%d 0\n",pos1+1); 81 else printf("%d 1\n",n-pos2); 82 } 83 } 84 return 0; 85 }