【bzoj3238】差異[AHOI2013](後綴數組+單調棧)
阿新 • • 發佈:2017-10-05
algorithm char tar 最小 one eight can ont 會有
題目傳送門:http://www.lydsy.com/JudgeOnline/problem.php?id=3238
這道題從大概半年以前就開始啃了,不過當年因為一些細節沒調出來,看了Sakits神犇的博客之後也沒明白自己掛在哪裏,於是就抄了個題解。然後現在突然想到填這個坑(其實是為了復習一下後綴數組模板),換了種思路才過了這道題……然而還是不知道當年那種寫法為什麽錯……似乎是掛在判重?
解法(Accepted):
首先我們可以觀察一下這個式子(見下),我們可以把它拆開,變成sigma(len(Ti)+len(Tj))-2*sigma(lcp(Ti,Tj))。其中前一項隨便在紙上算一算就能得出為n*(n+1)*(n-1)/2。
後面的lcp總和,我們可以先用後綴數組將其轉化為height數組的區間最小值之和。之前的寫法是統計以height數組每個值作為最小值的區間有多少個,再來算答案,然而似乎可能會有重復計算,要加一坨判斷。
但是我們可以改變一下統計方式,統計以每個數作為右端點的區間的最小值總和,然後把他們加起來。這裏就要用到單調棧一個神奇的性質:把一個序列壓進單調棧,這個序列的後綴最小/最大值一定會在單調棧中出現。於是可以轉化為統計單調棧中每個數對最小值之和的貢獻。計算方法見下圖:
此外,因為棧中每個數的貢獻從它被壓進棧到彈出是不會變的,於是只要在壓數和彈數時維護一下就能算出答案。
然後這道題就解決了^_^
代碼:
#include<cstdio> #include<cmath> #include<cstring> #include<cstdlib> #include<ctime> #include<algorithm> #include<queue> #include<vector> #define ll long long #define maxn 500010 inline ll read() { ll tmp=0; char c=getchar(),f=1; while(c<‘bzoj32380‘||‘9‘<c){if(c==‘-‘)f=-1; c=getchar();} while(‘0‘<=c&&c<=‘9‘){tmp=tmp*10+c-‘0‘; c=getchar();} return tmp*f; } char s[maxn]; ll sa[maxn],rk[maxn],tsa[maxn],trk[maxn],sum[maxn],h[maxn]; ll st[maxn]; ll n; void getsa() { int i,tot,cnt=256,len; for(i=1;i<=cnt;i++)sum[i]=0; for(i=0;i<n;i++)trk[i+1]=s[i]+1,++sum[trk[i+1]]; for(i=2;i<=cnt;i++)sum[i]+=sum[i-1]; for(i=n;i;i--)sa[sum[trk[i]]--]=i; cnt=1; rk[sa[1]]=1; for(i=2;i<=n;i++){ if(trk[sa[i]]!=trk[sa[i-1]])++cnt; rk[sa[i]]=cnt; } for(len=1;cnt<n;len<<=1){ for(i=1;i<=n;i++)trk[i]=rk[i]; for(i=1;i<=n;i++)sum[i]=0; tot=0; for(i=n-len+1;i<=n;i++)tsa[++tot]=i; for(i=1;i<=n;i++)if(sa[i]>len)tsa[++tot]=sa[i]-len; for(i=1;i<=n;i++)rk[i]=trk[tsa[i]],++sum[rk[i]]; for(i=2;i<=cnt;i++)sum[i]+=sum[i-1]; for(i=n;i;i--)sa[sum[rk[i]]--]=tsa[i]; cnt=1; rk[sa[1]]=1; for(i=2;i<=n;i++){ if(trk[sa[i]]!=trk[sa[i-1]]||trk[sa[i]+len]!=trk[sa[i-1]+len])++cnt; rk[sa[i]]=cnt; } } for(i=1;i<=n;i++){ if(rk[i]==1)continue; if(h[rk[i-1]]>0)h[rk[i]]=h[rk[i-1]]-1;else h[rk[i]]=0; while(s[i+h[rk[i]]-1]==s[sa[rk[i]-1]+h[rk[i]]-1])++h[rk[i]]; } } int main() { int i; scanf("%s",s); n=strlen(s); getsa(); ll top=0,ans=(n-1)*n*(n+1)/2,tot=0; st[0]=0; h[0]=-(1<<30); for(i=1;i<=n;i++){ while(h[st[top]]>=h[i]){ tot-=h[st[top]]*(st[top]-st[top-1]); --top; } tot+=h[i]*(i-st[top]); st[++top]=i; ans-=2*tot; } printf("%lld",ans); }
【bzoj3238】差異[AHOI2013](後綴數組+單調棧)