1. 程式人生 > 實用技巧 >題解 HDU6223 【Infinite Fraction Path】

題解 HDU6223 【Infinite Fraction Path】

這是一個作者歷經千辛萬苦,從無數次 \(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");
	}
}