HDU 4333 Revolving Digits(KMP:循环节+扩展KMP)
http://acm.hdu.edu.cn/showproblem.php?pid=4333
题意:给你一个n位的正整数X(<=10^100000),然后你依次将该整数的后1,2,3…,n位放到前面去,形成了一个新整数.问你这n个新整数中,有多少个比X小,与X相等,比X大?(新整数与原先的X均无前导0)
分析:
先用扩展KMP求出A[i]数组,A[i]数组表示以第i位为首部的前缀与串T的前缀最长公共部分.
假设我们当前要把从i到n-1的串移到串头去,那么此时A[i]=3,我们应该用A[i+3]与A[3]比较,就知道新串与原始串的大小关系.(想想是不是)
但是要注意如果A[i]+i==n(n为原始串的长度)的话,就不好判断了,因为你不知道超出串长还有多少位与原始串前缀相等(这里需要循环移位继续判断了).所以我们这里将原始串T变成串TT,即把原始串翻倍即可.然后用扩展KMP处理翻倍了的原始串.
然后对于位置[0,n-1]考虑其A[i]的值,如果A[i]+i>=n(n依然为T的长度)表示把[i,n-1]的串放到前面形成的新串与T正好相等.(想想是不是)
如果A[i]>0且A[i]+i<n,那么只需要比较TT[A[i]+i] 与TT[A[i]-1]的大小即可.
如果A[i]==0,那么首先看TT[i]是不是等于0,如果TT[i]!=0,那么直接比较TT[i]与TT[0].否则如果TT[i]==0,那么由于新数有前导0,所以新数的位数肯定少,所以新数必然变小,所以该情况也是直接比较TT[i]与TT[0]即可.
注意:如果两个不同的移动操作产生了同一个数的话,就只能算一次.通过测试可以发现只有串T是正好具有k(k>=2)个循环节的时候,才会出现重复串.否则不可能重复.
且当串T具有k个最短循环节的时候,每个新串被重复了K次.
AC代码:
#include <iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int MAXN=100000+1000; char T[MAXN*2]; int A[MAXN*2],next[MAXN]; int n;//原始串T的长度 void EKMP() { int j=0; A[0]=n; while(1+j<n*2 && T[0+j]==T[1+j]) j++; A[1]=j; int k=1; for(int i=2;i<n;i++) { int len=k+A[k]-1,L=A[i-k]; if(L<len-i+1) A[i]=L; else { j=max(0,len-i+1); while(i+j<2*n && T[0+j]==T[i+j]) j++; A[i]=j; k=i; } } } void getFail() { next[0]=next[1]=0; for(int i=1;i<n;i++) { int j=next[i]; while(j && T[i]!=T[j]) j=next[j]; next[i+1]=(T[i]==T[j])?j+1:0; } } int main() { int K; scanf("%d",&K); for(int kase=1;kase<=K;kase++) { scanf("%s",T); n=strlen(T); getFail(); for(int i=n;i<2*n;i++)//将串T变成串TT T[i]=T[i-n]; T[2*n]=0; EKMP(); int L=0,E=0,G=0; for(int i=0;i<n;i++) { if(A[i]>=n) E++; else//0<=A[i]<n { if(T[i+A[i]]>T[A[i]]) G++; else L++; } } if(next[n]>0 && n%(n-next[n])==0) { L/=n/(n-next[n]); E/=n/(n-next[n]); G/=n/(n-next[n]); } printf("Case %d: %d %d %d\n",kase,L,E,G); } return 0; }