前言
"I'm free."
做法與題解區都不同,雖然麻煩,但是畢竟複雜度是對的,而且想法很自然,還是寫一寫吧!
Solution
題意:給定長為 \(n\) 的字串 \(s\) 和長為 \(n\) 的陣列 \(A\),對於每個 \(r\),求 滿足 \(\text{LCP}(\text{Suffix}(x),\text{Suffix}(y))\ge r,x<y\) 的數對 \((x,y)\) 數量。
其中 \(\text{LCP}(x,y)\) 指字串 \(x,y\) 的最長公共字首,\(\text{Suffix}(i)\) 表示串 \(s\) 從第 \(i\) 位開始的字尾。
然後記數對 \((x,y)\) 權值為 \(A_x\times A_y\),對於每個 \(r\),求滿足上述條件的數對的最大權值。
一、串串題慣常套路
看見要求原串兩個字尾的 \(\text{LCP}\),最直接的反應便是啟動字尾陣列,並計算出 \(height\) 陣列。
然後 \(\text{LCP}(\text{Suffix}(x),\text{Suffix}(y))=\min_{i\in [rank_x+1,rank_y]} h_i,rank_x<rank_y\) 便可求出任意兩個字尾的 \(\text{LCP}\)。
現在我們就把字串問題轉移為數列問題。
二、具體解決方案
現在相當於詢問對於每個 \(r\),有多少滿足 \(\min_{i\in [x+1,y]}h_i \ge r,x<y\) 的數對 \((x,y)\)。
此時出現三個考慮方向:
第一是分別計算每一位 \(h_i\) 對答案貢獻。
第二是從大到小列舉 \(r\) ,使用並查集維護區間的阻斷或者連通性,計算答案是多少。
但是其實正序計算也是可以噠!
我們把 \(h_i\) 從小到大排序,並一個一個按原位置加入陣列中。
容易發現,加入所有 \(h_i<i\) 之後,仍然不包含任何數字的區間,就是對於 \(r=i\) 時的一個合法區間。
舉個例子:
s[i] | p | o | n | o | i | i | i | p | o | i |
---|---|---|---|---|---|---|---|---|---|---|
sa[i] | \(10\) | \(5\) | \(6\) | \(7\) | \(3\) | \(9\) | \(4\) | \(2\) | \(8\) | \(1\) |
h[i] | \(0\) | \(1\) | \(2\) | \(1\) | \(0\) | \(0\) | \(2\) | \(1\) | \(0\) | \(2\) |
加入 \(0\) | \(0\) | \(0\) | \(0\) | \(0\) |
\(\min_{i\in [2,4]}=1\),意味著 \(\text{LCP}(\text{Suffix}(sa_{2-1}),\text{Suffix}(sa_4))=1\),事實的確如此。
對於一個連續的空區間,任選其中兩點(左端點可以選到左邊的數字上),皆是一個合法答案,設其長度為 \(l\),對第一問貢獻為 \(l\times (l+1)\div 2\)。
考慮在陣列中加入一個數,會對答案產生什麼樣的影響?
會把一個連續空區間分成兩個。
那麼對於第一問是簡單的,減去原來長區間貢獻,分別加入兩個短區間貢獻即可。
三、第二問咋做
仍然考慮一個長區間對於整體的貢獻。
上文已經提到,對於一個連續的空區間,任選其中兩點(左端點可以選到左邊的數字上),皆是一個合法答案。
為了使得權值最大化,顯然要選 \(A_i\) 最大的兩個,或者最小的兩個(負負得正)。
這是一個典型的詢問區間最大和次大,使用資料結構維護即可。
然後對於區間的貢獻,不僅有刪除和新增(把一個大區間分成兩個後,刪除大區間貢獻,加入兩個小區間貢獻),還要實時查詢各個區間貢獻的最大值,還是用資料結構來維護。
四、實現細節
用啥資料結構呢?
詢問區間最大次大和最小次小,由於不帶修,ST 表即可。
(但是我倍增不熟,賽時怕掛,便使用了線段樹來維護)
如何找到左右最靠近的加入過的數,單調棧或者樹狀陣列或者 set 都可以。
(這裡建議最後一個,但是我賽時使用了最麻煩的第二個)
如何實時查詢貢獻最大值並支援加入刪除某個數?堆或者 multiset。
時間複雜度都是 \(\text{O}(n\log n)\),但是一個 \(\log\) 遍地跑,常數賊大。
(實測最慢點 \(970\) 到 \(1500\) 毫秒不等,透過率高於 \(50\%\))。
這個做法最大的區別便是正序列舉 \(r\),並使用堆來解決第二問。
貼一個修改過的賽時程式碼,很唐。
趣事:賽時沒有使用 multiset 而用的 set,但是過了所有測試資料,洛谷上被官方資料搞成 \(70\) 了。
"Save me."
AC Code
#include<bits/stdc++.h>
#define int long long
#define For(l,r,b) for(b=l;b<=r;b++)
#define N 300005
#define inf 1000000007ll
using namespace std;
string s;
int i,j,sa[N],rk[N],rk1[N],sec[N],tot[N],n,h[N];
int tr[N][2];
int a[N],A[N];
vector<int> hp[N];
struct tree{
int l,r,s1,s2,b1,b2;
}t[4*N];
struct an{
int s1,s2,b1,b2;
};
an mi(an x,an y)
{
return {min(x.s1,y.s1),min(max(x.s1,y.s1),min(x.s2,y.s2)),max(x.b1,y.b1),max(min(x.b1,y.b1),max(x.b2,y.b2))};
}
void pushup(int o)
{
an x={t[o*2].s1,t[o*2].s2,t[o*2].b1,t[o*2].b2},y={t[o*2+1].s1,t[o*2+1].s2,t[o*2+1].b1,t[o*2+1].b2};
an xy=mi(x,y);
t[o]={t[o].l,t[o].r,xy.s1,xy.s2,xy.b1,xy.b2};
}
multiset<int> se;
multiset<int>::iterator it;
void build(int l,int r,int o)
{
t[o].l=l;t[o].r=r;
if(l==r)
{
t[o].s1=t[o].b1=A[l];
t[o].s2=inf;
t[o].b2=-inf;
return;
}
int mid=(l+r)>>1;
build(l,mid,o*2);
build(mid+1,r,o*2+1);
pushup(o);
}
an query(int l,int r,int o)
{
if(t[o].l>=l&&t[o].r<=r) return {t[o].s1,t[o].s2,t[o].b1,t[o].b2};
int mid=(t[o].l+t[o].r)>>1;
an ans={inf,inf,-inf,-inf};
if(l<=mid) ans=mi(ans,query(l,r,o*2));
if(r>mid) ans=mi(ans,query(l,r,o*2+1));
return ans;
}
void change(int x,int y,int id)
{
x++;
for(;x<=n;x+=x&-x) tr[x][id]=max(tr[x][id],y);
}
int ask(int x,int id)
{
x++;
int ans=-1;
for(;x>0;x-=x&-x) ans=max(ans,tr[x][id]);
return ans;
}
signed main()
{
cin.tie(0)->sync_with_stdio(0);
cout.tie(0);
cin>>n>>s;
For(1,n,i) cin>>a[i];
s='#'+s;
For(1,n,i) tot[s[i]]++,rk[i]=s[i];
For(1,128,i) tot[i]+=tot[i-1];
for(i=n;i;i--) sa[tot[rk[i]]--]=i;
int sz=128;
for(int k=1;k<=n;k<<=1)
{
int cnt=0;
For(n-k+1,n,i) sec[++cnt]=i;
For(1,n,i) if(sa[i]>k) sec[++cnt]=sa[i]-k;
memset(tot+1,0,sz<<3);
For(1,n,i) ++tot[rk[i]];
For(1,sz,i) tot[i]+=tot[i-1];
for(i=n;i>=1;i--) sa[tot[rk[sec[i]]]--]=sec[i];
memcpy(rk1+1,rk+1,n<<3);
sz=1;
rk[sa[1]]=1;
For(2,n,i) rk[sa[i]]=(rk1[sa[i]]==rk1[sa[i-1]]&&rk1[sa[i]+k]==rk1[sa[i-1]+k])?sz:++sz;
if(sz==n) break;
}
For(1,n,i)
{
int pt=max(0ll,h[rk[i-1]]-1);
while(s[i+pt]==s[sa[rk[i]-1]+pt]) pt++;
h[rk[i]]=pt;
}
h[1]=0;
For(1,n,i) A[i]=a[sa[i]];
For(1,n,i) hp[h[i]].push_back(i);
build(1,n,1);
an as=query(1,n,1);
change(1,1,0);change(0,0,1);
long long ans=(n-1)*n/2,an2=max(as.b1*as.b2,as.s1*as.s2);
cout<<ans<<" "<<an2<<endl;
For(0,n-2,i)
{
for(auto j:hp[i])
{
int lf=ask(j-1,0),rf=n+1-ask(n-j,1);
if(j==1) lf++;
lf++,rf--;
an gx;int gp;
if(rf>=lf)
{
gx=query(lf-1,rf,1),gp=max(gx.b1*gx.b2,gx.s1*gx.s2);
it=se.lower_bound(-gp);
if(gp!=inf*inf&&j!=1)se.erase(se.lower_bound(-gp));
}
ans-=(rf-lf+1)*(rf-lf+2)/2;
ans+=(j+1-lf)*(j-lf)/2;
ans+=(rf-j+1)*(rf-j)/2;
if(j-1>=lf)
{
gx=query(lf-1,j-1,1),gp=max(gx.b1*gx.b2,gx.s1*gx.s2);
if(gp!=inf*inf) se.insert(-gp);
}
if(j+1<=rf)
{
gx=query(j,rf,1),gp=max(gx.b1*gx.b2,gx.s1*gx.s2);
if(gp!=inf*inf) se.insert(-gp);
}
change(j,j,0);change(n+1-j,n+1-j,1);
}
cout<<ans<<" "<<(ans==0?0:(-(*se.lower_bound(-inf*inf))))<<endl;
}
return 0;
}
後記
寫得比較認真的一篇題解。
謹以此題解,為我長達四天的字串簡單學習作結,也為基本 OI 知識的第一輪學習作結。
"I won't leave you."