題解 HDU6223 【Infinite Fraction Path】
阿新 • • 發佈:2020-07-17
這是一個作者歷經千辛萬苦,從無數次 \(WA\),\(RE\),\(TLE\) 中得到的心得體會與感悟。
這是一道字尾排序的進階題,是一道很好的題目,作者認為它可以很好的加深我們對於字尾排序的理解。
首先,我們可以很容易的判斷出來,這是一道字尾陣列的題目,因為他要求我們找出在一棵基環樹上,字典序最大的一個字尾。
但是,他與普通的字尾陣列又有著不同的地方,具體體現在如下:
-
由於是在基環樹上的字尾,所以長度是無限的(因為有環),題目要求我們找出字典序最大的長度為 \(n\) 的字尾,所以在處理 \(tp\) 陣列的時候,我們需要特殊處理。
-
同樣還是 \(tp\) 陣列的處理,因為我們是在樹上找第二關鍵詞,也就是上一次排序的結果,我們不能直接通過
針對第一條,我們可以輕易得到,如果一個長度大於 \(n\) 的字尾的字典序是最大的,那麼它長度為 \(n\) 的字首的字典序也一定是最大的,所以我們可以直接倍增。同時對於此時 \(tp\) 陣列的處理我們就可以不用考慮長度的限制(因為原本的 \(tp\) 陣列如果在長度不夠時,是直接變為最前的位置的)。
針對第二條,我們可以有多種方法來解決這個問題。
方法一:我們可以運用倍增,找到當前點往後 \(2^k\) 步後所在點的位置,而當前點的 \(tp\) 陣列就是由這個點的 \(sa\) 陣列所更新的,我們可以考慮用一個 \(vector\)
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\)
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");
}
}