您的位置:首页 > 其它

BZOJ 2882 浅谈最小表示法后缀数组求解+线性求解

2017-07-31 09:10 411 查看


世界真的很大

最小表示法这个东西很偏

一开始做这道题想到的是后缀数组的做法,nlogn的,后来借这个契机学了下最小表示法,重新写了一遍,O(n)的

想不通为什么n比nlogn快10倍。。

可能后缀数组的常数偏大吧

看题先:

description:

小敏和小燕是一对好朋友。
他们正在玩一种神奇的游戏,叫Minecraft。
他们现在要做一个由方块构成的长条工艺品。但是方块现在是乱的,而且由于机器的要求,他们只能做到把这个工艺品最左边的方块放到最右边。
他们想,在仅这一个操作下,最漂亮的工艺品能多漂亮。
两个工艺品美观的比较方法是,从头开始比较,如果第i个位置上方块不一样那么谁的瑕疵度小,那么谁就更漂亮,如果一样那么继续比较第i+1个方块。如果全都一样,那么这两个工艺品就一样漂亮。


input

第一行两个整数n,代表方块的数目。
第二行n个整数,每个整数按从左到右的顺序输出方块瑕疵度的值。


output

一行n个整数,代表最美观工艺品从左到右瑕疵度的值。


现在知道这东西就是一个裸的最小表示法

但第一次做的时候实在是不知道有这么个东西,思路是这样的

考虑重新排列之后字典序最小,想到后缀数组的后缀排名就是按后缀的字典序排的,但是这样有可能会忽略,若第一第二名的后缀,第一比第二短,其后一位接上第一位的时候,字典序反而会大于第二位

现在就需要解决这个东西了

考虑是让原序列从某个位置断开接到后面,可以想到是一个环,选择一个位置作为开始位置使得字典序最小

对于序列成环这种东西我们有经典解法,要记住,把原序列复制一遍接到后面,这样从原序列的每一个位置都是一个完整(多一点)的循环了

在这个构造上面求后缀排名第一的就行了,几乎没什么细节,后缀数组写熟了的话,也就10分钟吧

完整代码:

#include<stdio.h>
#include<algorithm>
using namespace std;

int n,mxn=0;
int wa[800010],wb[800010],wv[800010],ws[800010];
int rank[800010];
int sa[800010],r[800010];

bool cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}

void da(int *r,int *sa,int n,int m)
{
int *x=wa,*y=wb,*t;
for(int i=0;i<m;i++) ws[i]=0;
for(int i=0;i<n;i++) ws[x[i]=r[i]]++;
for(int i=1;i<m;i++) ws[i]+=ws[i-1];
for(int i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
for(int p=1,j=1;p<n;j<<=1,m=p)
{
p=0;
for(int i=n-j;i<n;i++) y[p++]=i;
for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(int i=0;i<n;i++) wv[i]=x[y[i]];
for(int i=0;i<m;i++) ws[i]=0;
for(int i=0;i<n;i++) ws[wv[i]]++;
for(int i=1;i<m;i++) ws[i]+=ws[i-1];
for(int i=n-1;i>=0;i--)
sa[--ws[wv[i]]]=y[i];
int i;
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]= cmp(y,sa[i-1],sa[i],j) ? p-1 : p++;
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&r[i]),mxn=max(mxn,r[i]);
for(int i=n;i<2*n;i++)
r[i]=r[i-n];
da(r,sa,2*n,mxn+1);
int x=sa[1]%n,cnt=0;
for(int i=x;i<n*2;i++)
{
cnt++;
printf("%d",r[i]);
if(cnt!=n) printf(" ");
else break ;
}
return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/


现在来谈谈最小表示法这个东西。虽然很偏,但是他的思想可能还是比较重要,而且,反正短,学起来也比较好懂,学呗

可以去看一下ZY大大的文章

文章里是判断两个字符串是不是同构,通过两个的最小表示法来判断

对于单独的一个字符串,也可以用同样的原理求其最小表示法

考虑两个指针i,j,从i,j向后匹配,设在i+k和j+k这个位置,字符串的值不同了,失配了,就是说i到i+k-1和j到j+k-1这两段是完全相同的,通过比较i+k和j+k的两个字符,就知道i到i+k和j到j+k这两段的字典序哪个小了

假设i+k的字典序较大,那么i到i+k都不可能是最小表示法的起始位置,因为至少存在j到j+k的同样位置使其字典序比他小

所以i可以直接跳到i+k+1的位置

考虑重复以上操作,如果i或者j已经是最小表示法的位置了,就绝不会再动了,因为找不到另一个位置的值比其小,而只有字典序较大的指针才会移动。

所以直到一个指针移动到末尾,那另一个指针就是最小表示法的起始位置了

大概就是这样,这个也需要把原序列复制后接到一起

细节上没什么要注意的,代码也很短

完整代码:

#include<stdio.h>
#include<algorithm>
using namespace std;

int n;
int a[800010];

int MEXP()
{
int i=1,j=2,k=0;
while(i<=n&&j<=n&&k<=n)
{
int tmp=a[i+k]-a[j+k];
if(!tmp) k++;
else
{
if(tmp>0) i+=k+1;
else j+=k+1;
if(i==j) i++;
k=0;
}
}
return min(i,j);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),a[i+n]=a[i];
int x=MEXP(),cnt=0;
for(int i=x;i<=2*n;i++)
{
cnt++;
printf("%d",a[i]);
if(cnt!=n) printf(" ");
else break ;
}
return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/


嗯,就是这样
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: