最小表示法——杨子曰算法

最小表示法——杨子曰算法

先给一道题:ZOJ-2006
就是说给你一个可以循环的字符串,问以哪个字符开头,它的字典序最小

今天我们曰最小表示法,他可以求出一个环之类的东西的最小表示——那有什么卵用呢?这样你就可以判断两个环是不是同一个了╰(๑◕ ▽ ◕๑)╯(还真没什么卵用)

上面那道题有人说O(n^2),就搞定了(呵呵)
我们用O(n)干掉它


好了,接下来我们就来曰最小表示怎么求:
这种算法使用两个指针i,j实现的,i,j,表示的是做到当前这个阶段以i,j为开头都有可能成为答案

接下来我要说几个重要的事情:
之后的过程中,我们要维护在[i,j]这个区间之前的不可能是答案,指针i和指针j之间不可能是答案(最后我会证明,为什么我们接下来的做法可以维护)

1.把字符串倍长一倍,因为它是一个环

2.i=0 j=1

3.我们开始挨个比较,用一个k从0开始,如果s[i+k]=s[j+k]那么k++

4.现在我们发现s[i+k]不等于s[j+k]了,也就是说从i开头的字符串和j开头的字符串不同了,那我们就要把字典序大的那个指针淘汰,因为以它开头已经tan90°了,也就是把它往后移,那往后移多少呢?,童鞋大胆提出想法,往后移一个,杨子曰:这个与暴力写法有什么区别,为了保证O(n)的复杂度我们至少也要把这个指针往后移k个单位,于是我们可以得出以下结论:

if (s[i+k]>s[j+k]) i=max(i+k+1,j+1);
if (s[i+k]<s[j+k]) j=max(j+k+1,i+1);

我来说明一下为什么这样写是对的:
Look at the 图,假设绿色部分都是相等的,而比到这种情况时红色大于黄色,所以我们要把j往后移
这里写图片描述
我们现在要说明的就是j到红色这一段都不可能(我们使用反正法,反正这样是对的,得证)反证法:
假设我们可以在这一段找到一个x是答案,那我们一定可以在i到黄色这一段找到一个y,使得L1这段等于L2这段,但是到黄色和红色这里时x就没有y优了,所以x不可能成为答案,这回真的得证
这里写图片描述
然后我再说为什么j可以移到i+1,如果能移到i+1,说明j一定在i前面,像这样:
这里写图片描述
还记得我们之前说过的重要的事情吗!So,这些地方都不可能了:
这里写图片描述
于是乎j就只能移到i后面了,懂否?

5.我们要一直循环做步骤3和4,直到什么时候呢?

两种情况:
①i>len 或 j>len 也就是两个指针中有一个出界了
这个很好理解,出界了就说明所有的可能情况都被我们检查过了,So,这时没出界的指针就是答案

②k=len 也就是说以i为开头和以j为开头的串完全一样
这种情况为什么就是答案了呢
两个串完全一样了我们就把两个串的开头i,j对齐,这样上下两两对应相等:
这里写图片描述
首先我们知道i以前和i到j这段区间都是不可能的,又因为这下两两对齐,So绿色部分就等于蓝色部分,于是乎蓝色部分也被我们out
这里写图片描述
同理,黄色部分又等于蓝色部分,So黄色部分也被我们out
这里写图片描述
于是无穷无尽地延伸,整条链都被我们排除了,i,j就成为了答案


还有最后一个事情只要证完它,这个算法就完美了——就是为什么这样做可以维护我们要维护在[i,j]这个区间之前的不可能是答案,指针i和指针j之间不可能是答案
(这里我们用红色来表示不可能的区间)假设我们已经做到了这一步:
这里写图片描述
1.如果要改变区间之前的那一段,只有可能是前面的指针移到后面,也就是这里的i移到j后面,如果我们要移动i说明i已经不可能是答案了,Look at the 图,一目了然,维护成功:
这里写图片描述
2.如果要改变两个指针之间的一段,移动两个指针都有可能,如果移动的是前面那个指针,那中间这段就直接没了!我们就不考虑了
我们只考虑移动后面那个指针,也就是这里的j要移到j+k+1去,我们在上面已经用反证法证明过了j到j+k不可能是答案,完美,再次维护成功:
这里写图片描述
OK,完事


c++模板(ZOJ2006):

#include<bits/stdc++.h> 
using namespace std;

string s;

int main(){
	int cas;
	scanf("%d",&cas);
	while(cas--){
		cin>>s;
		int len=s.length();
		s+=s;
		int i=0,j=1;
		while(i<len && j<len){
			int k=0;
			while(k<len && s[i+k]==s[j+k]) k++;
			if (k>=len) break;
			if (s[i+k]>s[j+k]) i=max(i+k+1,j+1);
			else j=max(j+k+1,i+1);
		}
		cout<<min(i,j)+1<<endl;
	} 
	return 0;
}

于XJ机房607

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值