大致思路:
这道题也是肝了很久的一道题。。
之前打死也没想通为啥可以用拓展kmp做,怎么是首尾比较法?后来直接写个例子就知道了。。。。
方法是这样的,比如说t=12312312312,那么我就把t重复一遍得到s:1231231231212312312312。惊奇的发现从s的任一位开始数n位,也就是t旋转所得的一种结果。
那么,当然extend[1]=n,后面的话,如果extend值为0说明第一位就不一样,那就直接比较第一位大小,如果extend值不为0的话说明前面是有几个数位是一样的,那就比较第一个不一样的数位即 t[1+extend[i]](注意数位从1开始)和s[i+extend[i]]啊!比如说这个例子中,第七位开始的子串是1231212312312312,extend[7]=5,就是说和原串t=12312312312的前五位撞了,那么就比较第六位啊!发现第六位原串是3子串是1,所以按这个情况旋转得到的是小于原数的数。
整体思路解决了。优化问题来了:由于一些旋转所得的数字可能有重复,为了去重,一般会想到用map来存出现的次数。这里其实也可以,不过数位一大就会超时和超内存(题目想这样卡你)。
所以这里有一个更好的更优的满分方法:用kmp的next数组求最小循环节!
先上结论:串长度n-next[n]=最小循环节长度len,如果n%len==0,说明这个串都是由最小循环节构成的。
来了解一下原理:
所以,针对这道题,如果我们找到原数字串中的最小循环节,如果n%len==0即真的只由最小循环节构成,那么也就是说原数字串旋转所得的数字(不重复)一共也就(最小循环节长度)那么多种!!(这个可以举例看看)。那么我就只需要在s串中找前len个结果拿来看就Ok了。
这里要强调一下,kmp和exkmp中都叫next数组,但含义上区别完全不同:
kmp是拿来匹配模式串的,就是拿来找子串匹配的,为了简化复杂度,设置了next数组。next数组的含义是next[i]表示只看原串1~i部分,其中,前头和末尾有相同的部分,其长度为next[i]。
exkmp是拿来头尾比较的,可以自身和自身比,也可以两个不同串来比。拿t的头和s的尾来比,看重复的部分有多少。首先需要getnext(),其中next[i]表示t和自身比,(t[i]~t的末尾)和t串重复了next[i]长度部分。(而extend和next原理含义是一样的。)然后getextend(),其中extend[i]表示t和s比,这个i是s的下标,表示(s[i]~s的末尾)和t串比较,重复了extend[i]长度的部分。
自己画了个图形象一点:
AC代码(getnext是exkmp的,getnext2是kmp的):
#include<iostream> #include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; char t[maxn],s[maxn<<1]; int nextt[maxn]; int next2[maxn]; int extend[maxn<<1]; int n,m; //t和s的长度 int vis[maxn]; //防重复 void getnext() { nextt[1]=n; int a=0; for(int i=1;i<=n && t[i]==t[i+1];i++) a++; nextt[2]=a; int k=2,p,l; for(int i=3;i<=n;i++) { p=k+nextt[k]-1; l=nextt[i-k+1]; if(p-i>=l) nextt[i]=l; else { int j=p-i+1; if(j<0) j=0; while(i+j<=n && t[1+j]==t[i+j]) j++; nextt[i]=j; k=i; } } } void getnext2() { next2[1]=0; for(int i=2;i<=n;i++) { int j=next2[i-1]; while(t[j+1]!=t[i] && j>0) j=next2[j]; if(t[j+1]==t[i]) next2[i]=j+1; else next2[i]=0; } } void getextend() { int a=0; for(int i=1;t[i]==s[i];i++) a++; extend[1]=a; int k=1,p,l; for(int i=2;i<=m;i++) { p=k+nextt[k]-1; l=nextt[i-k+1]; if(p-i>=l) extend[i]=l; else { int j=p-i+1; if(j<0) j=0; while(i+j<=m && 1+j<=n && t[1+j]==s[i+j]) j++; extend[i]=j; k=i; } } } int main() { scanf("%s",t+1); n=strlen(t+1); m=n*2; strcpy(s+1,t+1); strcat(s+1,t+1); getnext(); getnext2(); int len=n-next2[n]; //这里通过最小循环节优化去重超级关键了! if(m%len!=0) len=n; getextend(); int x=0,y=1,z=0; //x是小于原数的个数。z是大于原数的个数。 for(int i=1;i<=len;i++) //只需要看前len个。 { if(extend[i]==n) continue; if(t[1+extend[i]]>s[i+extend[i]]) x++; else if(t[1+extend[i]]<s[i+extend[i]]) z++; } cout<<x<<" "<<y<<" "<<z; return 0; }
PS:这里必须要注意一个关于next数组定义的问题,如果写int next[maxn]是通不过devcpp的,因为next这个数组名容易歧义,编译报错,所以以后注意一下都写int nextt[maxn],这样!