CF862F Mahmoud and Ehab and the final stage 题解
根号分治(线段树+单调栈)
Statement
CF862F Mahmoud and Ehab and the final stage - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有 个字符串和 次操作:
- 表示询问区间 的所有子区间 中, 的最大值。
- 表示把第 个字符串改成 。
Solution
显然,如果询问 ,答案就是 的长度
欲求区间的最长公共前缀,套路地设 ,那么
直接暴力扫过去就好,毕竟
所以我们容易得到一个暴力的 做法,即每个询问枚举两个端点然后线段树求解
显然不行,主要矛盾在端点枚举量,不妨钦定 就是我们的最小值,设 表示 左/右 第一个小于 的位置,容易发现满足 是最小值的的区间就是 ,以 为最小值的贡献就是
数组我们可以使用单调栈 求解,所以现在的复杂度变成了 ,甚至干掉了最小值线段树
发现这个方法好像不能再优化了,它的局限性在于这个 ,如果数量少一点,是完全可做的
注意到一个重要性质: ,我们考虑根号分治,设闸值
- 对于 的部分,这部分的元素数量不超过 ,我们可以直接采用上述方法,
- 对于 的部分,我们仿照上述思路,想要令值 为所钦定的最小值(注意,不是令某个 ),我们所可以选取的 都应该满足 ,不妨把 的位置 标为 ,否则标 ,那么问题就变成了求 最长连续 段长 ,贡献就是 我们可以开 棵线段树解决。
这样时间复杂度一共是 ,当 时有最优 。
小心 棵线段树 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2021-02-06 [POJ3076] Sudoku