标签:str 排名 printf action 操作 意义 its def c++
这是一道后缀排序的进阶题,是一道很好的题目,作者认为它可以很好的加深我们对于后缀排序的理解。
首先,我们可以很容易的判断出来,这是一道后缀数组的题目,因为他要求我们找出在一棵基环树上,字典序最大的一个后缀。
但是,他与普通的后缀数组又有着不同的地方,具体体现在如下:
由于是在基环树上的后缀,所以长度是无限的(因为有环),题目要求我们找出字典序最大的长度为 \(n\) 的后缀,所以在处理 \(tp\) 数组的时候,我们需要特殊处理。
同样还是 \(tp\) 数组的处理,因为我们是在树上找第二关键词,也就是上一次排序的结果,我们不能直接通过上一次的 \(sa\) 数组的加减来获得 \(tp\) 数组的位置。
针对第一条,我们可以轻易得到,如果一个长度大于 \(n\) 的后缀的字典序是最大的,那么它长度为 \(n\) 的前缀的字典序也一定是最大的,所以我们可以直接倍增。同时对于此时 \(tp\) 数组的处理我们就可以不用考虑长度的限制(因为原本的 \(tp\) 数组如果在长度不够时,是直接变为最前的位置的)。
针对第二条,我们可以有多种方法来解决这个问题。
方法一:我们可以运用倍增,找到当前点往后 \(2^k\) 步后所在点的位置,而当前点的 \(tp\) 数组就是由这个点的 \(sa\) 数组所更新的,我们可以考虑用一个 \(vector\) 或者是 \(queue\) (反正可以存储数据都可以)在往后 \(2^k\) 步的点的下标位来存下当前点,最后在遍历一遍 \(sa\) 数组,同时调用出 \(queue\) 中存储的点,来更新 \(tp\) 数组。代码如下:
int tmp=0;
for(int i=1;i<=n;++i)
q[to[i][k]].push(i);
for(int i=1;i<=n;++i)
{
while(q[sa[i]].size())
{
tp[++tmp]=q[sa[i]].front();
q[sa[i]].pop();
}
}
方法二:我们可以回忆一下运用在后缀数组中的基数排序写法:是先运用一个桶数组,搞一遍前缀和来得出在第一关键词 \(rk\) 下每一种关键词的排名区间,在倒着遍历 \(tp\) 数组,就可以得到 \(sa\) 数组了。我们知道, \(tp\) 数组和 \(sa\) 数组的意义是完全一样的,即 \(tp_i\) 表示,排名为 \(i\) 的后缀的编号。所以我们完全可以利用 \(rk\) 数组重新计算一遍当前点往后 \(2^k\) 步后的 \(tp\) 数组,具体操作十分类似于我们第一次进行的基数排序,只不过加入点的 \(rk\) 时要加上倍增数组。代码如下:
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;++i)
tax[rk[to[i][k]]]++;
for(int i=1;i<=m;++i)
tax[i]+=tax[i-1];
for(int i=1;i<=n;++i)
tp[tax[rk[to[i][k]]]--]=i;
两种方法都是正确的,但是方法二的时间复杂度好像更优秀一点(原因是方法一我 \(T\) 飞了???)。而剩下的部分,就是利用得到的后缀数组直接模拟 \(n\) 遍得到答案了。
具体代码如下(方法二):
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=150005;
int t,T,n,m;
int a[N],to[N][25];
int sa[N],rk[N],tp[N],tax[N];
void qsort()
{
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;++i)
tax[rk[i]]++;
for(int i=1;i<=m;++i)
tax[i]+=tax[i-1];
for(int i=n;i>=1;--i)
sa[tax[rk[tp[i]]]--]=tp[i];
}
void SA()
{
for(int i=1;i<=n;++i)
{
rk[i]=a[i];
tp[i]=i;
}
m=10;
qsort();
for(int k=0;(1<<k)<=n;++k)
{
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;++i)
tax[rk[to[i][k]]]++;
for(int i=1;i<=m;++i)
tax[i]+=tax[i-1];
for(int i=1;i<=n;++i)
tp[tax[rk[to[i][k]]]--]=i;
qsort();
swap(tp,rk);
int tmp=rk[sa[1]]=1;
for(int i=2;i<=n;++i)
{
if(tp[sa[i-1]]==tp[sa[i]]&&tp[to[sa[i-1]][k]]==tp[to[sa[i]][k]])
rk[sa[i]]=tmp;
else
rk[sa[i]]=++tmp;
}
m=tmp;
if(m>=n)
break;
}
}
int main()
{
cin>>T;
while(++t<=T)
{
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%1d",&a[i]);
++a[i];
to[i][0]=((ll)(i-1)*(i-1)+1)%n+1;
}
for(int i=1;i<=20;++i)
{
for(int j=1;j<=n;++j)
{
to[j][i]=to[to[j][i-1]][i-1];
}
}
SA();
printf("Case #%d: ",t);
int tmp=sa[n];
for(int i=1;i<=n;++i)
{
printf("%d",a[tmp]-1);
tmp=to[tmp][0];
}
printf("\n");
}
}
题解 HDU6223 【Infinite Fraction Path】
标签:str 排名 printf action 操作 意义 its def c++
原文地址:https://www.cnblogs.com/Point-King/p/13332361.html