码迷,mamicode.com
首页 > 其他好文 > 详细

题解 HDU6223 【Infinite Fraction Path】

时间:2020-07-17 22:30:57      阅读:116      评论:0      收藏:0      [点我收藏+]

标签:str   排名   printf   action   操作   意义   its   def   c++   

这是一个作者历经千辛万苦,从无数次 \(WA\)\(RE\)\(TLE\) 中得到的心得体会与感悟。

这是一道后缀排序的进阶题,是一道很好的题目,作者认为它可以很好的加深我们对于后缀排序的理解。

首先,我们可以很容易的判断出来,这是一道后缀数组的题目,因为他要求我们找出在一棵基环树上,字典序最大的一个后缀。

但是,他与普通的后缀数组又有着不同的地方,具体体现在如下:

  1. 由于是在基环树上的后缀,所以长度是无限的(因为有环),题目要求我们找出字典序最大的长度为 \(n\) 的后缀,所以在处理 \(tp\) 数组的时候,我们需要特殊处理。

  2. 同样还是 \(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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!