字符串最小表示初探 By cellur925

我们考虑有一个字符串,可以从这个字符串的不同位置出发,把这个字符串大声朗读出来,当到字符串末端的时候再从头开始读,直到回到“梦开始的地方”。

设字符串长度为(n),那么有(n)种不同的读法。我们现在想要在这些读法中找一个字符串使得他字典序最小,如何快速求出?

我们当然可以用其他朴素的方法(这里不再赘述),但其实我们有更高效的线性算法:最小表示法。

算法描述

  1. 首先把这个字符串复制二倍接在后面(类似断环为链)

  2. 然后利用两个指针(i=1)(j=2)(k=0)的帮助下向后扫,当遇到(i+k)(j+k)位置的字符不相等时,就退出。

  3. 如果(i+k)位置更大一些,直接把(i)跳到(i+k+1)。因为可以证明从(i+1)(i+k)都不是字符串的最小表示,扫这部分就是冗余的。

  4. 两个指针不断尝试向后移动,一个移动到结尾就停止扫描,保证复杂度在(O(n))内。

void work()
{
	n=strlen(str+1);ans=0;
	for(int i=1;i<=n;i++) str[n+i]=str[i];
	int i=1,j=2,k;
	while(i<=n&&j<=n)
	{
		for(k=0;k<=n&&str[i+k]==str[j+k];k++);
		if(k>=n) break;
		if(str[i+k]>str[j+k])
		{i=i+k+1;if(i==j) i++;}
		else
		{j=j+k+1;if(i==j) j++;}
	}
	ans=min(i,j);
	printf("%d
",ans);
}

两道新鲜热乎的例题

例1:bzoj1398寻找主人

给你两个字符串,问你他们是否同构,若同构输出最小表示。

第二问是裸题,第一问我们只要分别求出两个字符串的最小表示看他们是否相同即可。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>

using namespace std;

bool flag;
int pos1,pos2,len;
char s1[3000000],s2[3000000];

void work1()
{
	len=strlen(s1+1);
	for(int i=1;i<=len;i++) s1[i+len]=s1[i];
	int i=1,j=2,k;
	while(i<=len&&j<=len)
	{
		for(k=0;k<=len&&s1[i+k]==s1[j+k];k++);
		if(k>=len) break;
		if(s1[i+k]>s1[j+k])
		{
			i=i+k+1;
			if(i==j) i++;
		} 
		else
		{
			j=j+k+1;
			if(i==j) j++;
		} 
	}
	pos1=min(i,j);
}

void work2()
{
	for(int i=1;i<=len;i++) s2[i+len]=s2[i];
	int i=1,j=2,k;
	while(i<=len&&j<=len)
	{
		for(k=0;k<=len&&s2[i+k]==s2[j+k];k++);
		if(k>=len) break;
		if(s2[i+k]>s2[j+k])
		{
			i=i+k+1;
			if(i==j) i++;
		} 
		else
		{
			j=j+k+1;
			if(i==j) j++;
		} 
	}
	pos2=min(i,j);
}

int main()
{
	scanf("%s",s1+1);
	scanf("%s",s2+1);
	work1();work2();
	int i=pos1,j=pos2,cnt=1;
	while(cnt<=len)
	{
		if(s1[i]==s2[j]) i++,j++,cnt++;
		else {flag=1;break;}
	}
	if(flag)
	{
		printf("No
");
		return 0;
	}
	else printf("Yes
");
	cnt=1;i=pos1;
	while(cnt<=len) cout<<s1[i],i++,cnt++;
	return 0;
}

例2 poj1509

给你很多字符串,求出他们最小表示的起点位置。

真·裸题。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int n,Q,ans;
char str[30000];

void work()
{
	n=strlen(str+1);ans=0;
	for(int i=1;i<=n;i++) str[n+i]=str[i];
	int i=1,j=2,k;
	while(i<=n&&j<=n)
	{
		for(k=0;k<=n&&str[i+k]==str[j+k];k++);
		if(k>=n) break;
		if(str[i+k]>str[j+k])
		{i=i+k+1;if(i==j) i++;}
		else
		{j=j+k+1;if(i==j) j++;}
	}
	ans=min(i,j);
	printf("%d
",ans);
}

int main()
{
	scanf("%d",&Q);
	while(Q--)
	{
		scanf("%s",str+1);
		work();
	}
	return 0;
}

感觉这种算法可扩展性不太强(?),不过当个暴力工具就好了(qwq)

原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9887155.html