【拓展KMP+最小循环节】旋转数字


大致思路

这道题也是肝了很久的一道题。。

之前打死也没想通为啥可以用拓展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],这样!


猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/80297803