今天做后缀数组练习时,偶遇poj3581,这题并不难,但是感觉思路比较巧妙,而且学习了一下离散化的思想,所以做一下记录
链接:http://poj.org/problem?id=3581
题目大意是给你一个串,你把它分成三段,并且把每一段翻转,求字典序最小
一开始我的想法是:从头到Rank最小的后缀为一段,拿掉这一段后,又从断口到Rank最小的后缀为一段,剩下的为一段
但是很快就发现这个思路是不正确的,以5 0 3 1 2 3 1 4为例,他对应的sa[1~8]数组结果是1 3 6 4 2 5 7 0,那么,根据我的思路得到的最终结果就是0 5 1 3 4 1 3 2,而正确的结果应该是0 5 1 3 2 1 3 4,问题就出在当串中存在相同值的情况,这是应该结合前后值来判断
我借鉴了网上的思路:http://blog.163.com/just_gogo/blog/static/1914390652011823103842787/
稍微分析一下:首先获得第一个下标肯定是字典序最小的那个没有疑问,关键就在于第二个下标,如果把第一部分去掉之后,还存在相同的值,可能存在一个后缀由于元素个数少而字典序靠前,经过翻转处理之后,可能后面又接上其他元素,导致字典序变大,所以在处理第二个下标时,应该把前面的元素贴到后面来做成新的串,然后在求sa数组,从而获得第二个下标(语文没学好,解释不清。。。)
除此以外,这道题还教会了我离散化的思想,我简单的说一下:就是当数据的上下限没给,只有数据个数时,可以通过结构体加排序,用排名来代替数值。
代码实现:
#include<stdio.h>
#include<algorithm>
using namespace std;
int str[200005],sa[200005],a[200005],b[200005],c[200005],index1,index2,n;
struct NUM//利用结构体离散化
{
int id,val,Rank;
}num[200005];
bool cmp1(const NUM &a,const NUM &b)//按值排序
{
return b.val>a.val;
}
bool cmp2(const NUM &a,const NUM &b)//按输入顺序排序
{
return b.id>a.id;
}
void getRank(int top)
{
int p=2;
num[0].Rank=1;
for(int i=1;i<top;i++)
num[i].Rank=(num[i].val==num[i-1].val)?p-1:p++;
}
void getStr(int top)
{
for(int i=0;i<top;i++)
str[top-1-num[i].id]=num[i].Rank;
}
void DA(int top,int m)
{
int *x=a,*y=b;
for(int i=0;i<m;i++)
c[i]=0;
for(int i=0;i<top;i++)
c[x[i]=str[i]]++;
for(int i=1;i<m;i++)
c[i]+=c[i-1];
for(int i=top-1;i>=0;i--)
sa[--c[x[i]]]=i;
for(int k=1;k<=top;k*=2)
{
int p=0;
for(int i=top-k;i<top;i++)
y[p++]=i;
for(int i=0;i<top;i++)
if(sa[i]>=k)
y[p++]=sa[i]-k;
for(int i=0;i<m;i++)
c[i]=0;
for(int i=0;i<top;i++)
c[x[y[i]]]++;
for(int i=1;i<m;i++)
c[i]+=c[i-1];
for(int i=top-1;i>=0;i--)
sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;
x[sa[0]]=0;
for(int i=1;i<top;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p-1:p++;
if(p>=top)
break;
m=p;
}
}
int update(int index)
{
int top=index;
for(int i=0;i<index;i++)//把前面的值贴到后面
str[top++]=str[i];
str[top]=0;//补零准备DA
return top;
}
void show(int index,int top)
{
for(int i=index;i<top;i++)
printf("%d\n",num[n-1-i].val);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&num[i].val);
num[i].id=i;
}
sort(num,num+n,cmp1);//排序获得Rank(离散化),用Rank获得sa
getRank(n);
getStr(n);
str[n]=0;
sort(num,num+n,cmp2);//还原数据顺序
DA(n+1,200002);
index1=sa[1]>1?sa[1]:(sa[2]>1?sa[2]:sa[3]);//第一个下标要保证还剩下2个及以上的数据
show(index1,n);
int temp=update(index1);//利用index1更新一下str
DA(temp+1,200002);
for(int i=0;;i++)//第二个下标要保证还剩下1个及以上的数据,而且肯定在index1之前,因为其他都是补上去的
{
if(sa[i]>=index1||sa[i]==0)
continue;
else
{
index2=sa[i];
break;
}
}
show(index2,index1);
show(0,index2);
return 0;
}