CF862F Mahmoud and Ehab and the final stage 题解
根号分治(线段树+单调栈)
Statement
CF862F Mahmoud and Ehab and the final stage - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有 \(n\) 个字符串和 \(q\) 次操作:
- \(1\ l\ r\) 表示询问区间 \([l,r]\) 的所有子区间 \([a,b]\) 中,\(LCP(s_a,s_{a+1},\dots, s_b)\times (b-a+1)\) 的最大值。
- \(2\ x\ str\) 表示把第 \(x\) 个字符串改成 \(str\)。
\(n,q,\sum|s|\le 10^5\)
Solution
显然,如果询问 \(l=r\) ,答案就是 \(s_i\) 的长度
欲求区间的最长公共前缀,套路地设 \(a_i=lcp(s_i,s_{i+1})\) ,那么 \(lcp(s_l,s_{l+1},\dots,s_r)=\min_\limits{l\le i<r}\{a_i\}\)
\(lcp(s_i,s_{i+1})\) 直接暴力扫过去就好,毕竟 \(\sum|s|\le 10^5\)
所以我们容易得到一个暴力的 \(O(q(n^2\log n))\) 做法,即每个询问枚举两个端点然后线段树求解
显然不行,主要矛盾在端点枚举量,不妨钦定 \(a_i(l\le i< r)\) 就是我们的最小值,设 \(L/R[i]\) 表示 \(i\) 左/右 第一个小于 \(a_i\) 的位置,容易发现满足 \(a_i\) 是最小值的的区间就是 \((L[i],R[i])\),以 \(a_i\) 为最小值的贡献就是 \(a_i\times(R[i]-L[i])\)
\(L,R\) 数组我们可以使用单调栈 \(O(n)\) 求解,所以现在的复杂度变成了 \(O(qn)\),甚至干掉了最小值线段树
发现这个方法好像不能再优化了,它的局限性在于这个 \(O(n)\) ,如果数量少一点,是完全可做的
注意到一个重要性质:\(\sum|s|\le 10^5\) ,我们考虑根号分治,设闸值 \(T\)
- 对于 \(a_i>T\) 的部分,这部分的元素数量不超过 \(\dfrac {\sum|s|}T\) ,我们可以直接采用上述方法,\(O(q\frac{\sum|s|}T)\)
- 对于 \(a_i\le T\) 的部分,我们仿照上述思路,想要令值 \(val\) 为所钦定的最小值(注意,不是令某个 \(a_i\)),我们所可以选取的 \(a_i\) 都应该满足 \(a_i\ge val\) ,不妨把 \(a_i\ge val\) 的位置 \(i\) 标为 \(1\) ,否则标 \(0\) ,那么问题就变成了求 \([l,r)\) 最长连续 \(1\) 段长 \(len\),贡献就是 \(len\times val\)我们可以开 \(T\) 棵线段树解决。\(O(qT\log n)\)
这样时间复杂度一共是 \(O(q(\dfrac {\sum|s|}T+T\log n))\) ,当 \(T=\sqrt{\dfrac{\sum |s|}{\log n}}\) 时有最优 \(O(q\sqrt{(\sum|s|)\log n})\) 。
小心 \(T\) 棵线段树 MLE
Code
#include<bits/stdc++.h>
#define ls lc[rt]
#define rs rc[rt]
#define mid ((l+r)>>1)
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
typedef long long ll;
const int N = 1e5+5;
const int T = 30;
int a[N],lc[N<<1],rc[N<<1];//lc rc 的作用在于后面的线段树空间只需要开2倍
string s[N];
int n,q,tmp,siz;
struct Segment_maxrange{//最长连续段
struct Tree{
int mx,lm,rm; bool cv;
Tree(int mx=0,int lm=0,int rm=0,bool cv=false)
:mx(mx),lm(lm),rm(rm),cv(cv){}
}t[N<<2];
int val;
void pushup(int rt){
t[rt]=Tree(max(max(t[ls].mx,t[rs].mx),t[ls].rm+t[rs].lm),
t[ls].lm+t[ls].cv*t[rs].lm,t[rs].rm+t[rs].cv*t[ls].rm,t[ls].cv&&t[rs].cv);
}
Tree pushup(Tree a,Tree b){
return Tree(max(max(a.mx,b.mx),a.rm+b.lm),
a.lm+a.cv*b.lm,b.rm+b.cv*a.rm,a.cv&&b.cv);
}
void build(int l,int r,int rt){
if(l==r){
if(a[l]>=val)t[rt]=Tree(1,1,1,1);
else t[rt]=Tree(0,0,0,0);
return ;
}
build(l,mid,ls),build(mid+1,r,rs);
pushup(rt);
}
void alter(int l,int r,int rt,int id){
if(l==r){
if(a[l]>=val)t[rt]=Tree(1,1,1,1);
else t[rt]=Tree(0,0,0,0);
return ;
}
id<=mid?alter(l,mid,ls,id):alter(mid+1,r,rs,id);
pushup(rt);
}
Tree query(int l,int r,int rt,int L,int R){
if(L<=l&&r<=R)return t[rt];
if(R<=mid)return query(l,mid,ls,L,R);
if(L>mid)return query(mid+1,r,rs,L,R);
return pushup(query(l,mid,ls,L,R),query(mid+1,r,rs,L,R));
}
};
struct Segment_maxval{//最大值线段树
int t[N<<2];
void pushup(int rt){t[rt]=max(t[ls],t[rs]);}
void build(int l,int r,int rt){
if(l==r)return t[rt]=s[l].size(),void();
build(l,mid,ls),build(mid+1,r,rs),pushup(rt);
}
void alter(int l,int r,int rt,int id){
if(l==r)return t[rt]=s[l].size(),void();
id<=mid?alter(l,mid,ls,id):alter(mid+1,r,rs,id);
pushup(rt);
}
int query(int l,int r,int rt,int L,int R){
if(L<=l&&r<=R)return t[rt];
if(R<=mid)return query(l,mid,ls,L,R);
if(L>mid)return query(mid+1,r,rs,L,R);
return max(query(l,mid,ls,L,R),query(mid+1,r,rs,L,R));
}
};
struct Segment_minval{//最小值线段树
int t[N<<2];
void pushup(int rt){t[rt]=min(t[ls],t[rs]);}
void build(int l,int r,int rt){
if(l==r)return t[rt]=a[l],void();
build(l,mid,ls),build(mid+1,r,rs),pushup(rt);
}
void alter(int l,int r,int rt,int id){
if(l==r)return t[rt]=a[l],void();
id<=mid?alter(l,mid,ls,id):alter(mid+1,r,rs,id);
pushup(rt);
}
int query(int l,int r,int rt,int L,int R){
if(L<=l&&r<=R)return t[rt];
if(R<=mid)return query(l,mid,ls,L,R);
if(L>mid)return query(mid+1,r,rs,L,R);
return min(query(l,mid,ls,L,R),query(mid+1,r,rs,L,R));
}
};
int lcp(string a,string b){//暴力 lcp
int n=min(a.size(),b.size()),res=0;
while(res<n&&a[res]==b[res])res++;
return res;
}
void build(int l,int r,int& rt){
rt=++siz; if(l==r)return ;
build(l,mid,ls),build(mid+1,r,rs);
}
namespace Subtask1{//O(qn^2\log n)
Segment_minval t;
ll query(int l,int r){
if(l==r)return s[l].size();
ll res=0;
for(int i=l;i<=r;++i)res=max(res,s[i].size());
for(int i=l;i<=r;++i)
for(int j=i+1;j<=r;++j)
res=max(res,1ll*(j-i+1)*t.query(1,n,1,i,j-1));
return res;
}
void solve(){
for(int i=1;i<n;++i)a[i]=lcp(s[i],s[i+1]);
build(1,n,tmp),t.build(1,n,1);
for(int i=1,op,l,r;i<=q;++i){
cin>>op>>l;
if(op==1)cin>>r,cout<<query(l,r)<<'\n';
else{
cin>>s[l];
if(l>1)a[l-1]=lcp(s[l-1],s[l]),t.alter(1,n,1,l-1);
if(l<n)a[l]=lcp(s[l],s[l+1]),t.alter(1,n,1,l);
}
}
}
}
namespace Subtask2{//O(qn)
int L[N],R[N],stk[N];
ll query(int l,int r){
if(l==r)return s[l].size();
ll res=0,top=0;
for(int i=l;i<=r;++i)res=max(res,s[i].size());
r--;
for(int i=l;i<=r;++i)R[i]=r+1;
stk[top=1]=l;
for(int i=l+1;i<=r;++i){
while(top&&a[stk[top]]>a[i])R[stk[top--]]=i;
stk[++top]=i;
}
for(int i=l;i<=r;++i)L[i]=l-1;//
stk[top=1]=r;
for(int i=r-1;i>=l;--i){
while(top&&a[stk[top]]>a[i])L[stk[top--]]=i;
stk[++top]=i;
}
for(int i=l;i<=r;++i)
res=max(res,1ll*(R[i]-L[i])*a[i]);
return res;
}
void solve(){
for(int i=1;i<n;++i)a[i]=lcp(s[i],s[i+1]);
for(int i=1,op,l,r;i<=q;++i){
cin>>op>>l;
if(op==1)cin>>r,cout<<query(l,r)<<'\n';
else{
cin>>s[l];
if(l>1)a[l-1]=lcp(s[l-1],s[l]);
if(l<n)a[l]=lcp(s[l],s[l+1]);
}
}
}
}
namespace Subtask3{//O(q\sqrt{(\sum|s|)\log n})
int L[N],R[N],stk[N];
vector<int>pos;//记录 a_i>T 的 i
Segment_maxrange t[T+1];
Segment_maxval t2;
void alter(int id){//a[id] 改变
int nw=lcp(s[id],s[id+1]);
if(a[id]>T&&nw<=T)pos.erase(lower_bound(pos.begin(),pos.end(),id));
if(a[id]<=T&&nw>T)pos.insert(lower_bound(pos.begin(),pos.end(),id),id);
swap(a[id],nw);////
for(int i=1;i<=min(T,max(nw,a[id]));++i)//取max有清除之用
t[i].alter(1,n,1,id);
}
ll calc(int l,int r){//单调栈
ll res=0,top=0;
for(int i=l;i<=r;++i)R[i]=r+1;
stk[top=1]=l;
for(int i=l+1;i<=r;++i){
while(top&&a[stk[top]]>a[i])R[stk[top--]]=i;
stk[++top]=i;
}
for(int i=l;i<=r;++i)L[i]=l-1;//
stk[top=1]=r;
for(int i=r-1;i>=l;--i){
while(top&&a[stk[top]]>a[i])L[stk[top--]]=i;
stk[++top]=i;
}
for(int i=l;i<=r;++i)
res=max(res,1ll*(R[i]-L[i])*a[i]);
return res;
}
ll query(int l,int r){
if(l==r)return s[l].size();
ll res=t2.query(1,n,1,l,r);//只选择1个字符串
for(int i=1;i<=T;++i){
int len=t[i].query(1,n,1,l,r-1).mx;
if(len)res=max(res,1ll*(len+1)*i);//求的是 a_i 最长连续,反映到 s 上 len+1
else break;
}
int L=lower_bound(pos.begin(),pos.end(),l)-pos.begin(),R;
for(;L<(int)pos.size()&&pos[L]<r;L=R+1){
for(R=L;R+1<(int)pos.size()&&pos[R+1]<r&&pos[R+1]==pos[R]+1;++R);//找到连续段
res=max(res,calc(pos[L],pos[R]));
}
return res;
}
void solve(){
build(1,n,tmp);
for(int i=1;i<n;++i)
a[i]=lcp(s[i],s[i+1]),
(a[i]>T&&(pos.push_back(i),1));
for(int i=1;i<=T;++i)
t[i].val=i,t[i].build(1,n,1);
t2.build(1,n,1);
for(int i=1,op,l,r;i<=q;++i){
cin>>op>>l;
if(op==1)cin>>r,cout<<query(l,r)<<"\n";
else{
cin>>s[l];
t2.alter(1,n,1,l);
if(l>1)alter(l-1);
if(l<n)alter(l);
}
}
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;++i)cin>>s[i];
if(n<=3000)Subtask1::solve();
else if(n<=10000)Subtask2::solve();
else Subtask3::solve();
return 0;
}