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(sa,sa+1,,sb)×(ba+1) 的最大值。
  • 2 x str 表示把第 x 个字符串改成 str​。

n,q,|s|105

Solution

显然,如果询问 l=r ,答案就是 si 的长度

欲求区间的最长公共前缀,套路地设 ai=lcp(si,si+1)​ ,那么 lcp(sl,sl+1,,sr)=minli<r{ai}

lcp(si,si+1) 直接暴力扫过去就好,毕竟 |s|105

所以我们容易得到一个暴力的 O(q(n2logn))​ 做法,即每个询问枚举两个端点然后线段树求解

显然不行,主要矛盾在端点枚举量,不妨钦定 ai(li<r) 就是我们的最小值,设 L/R[i] 表示 i 左/右 第一个小于 ai 的位置,容易发现满足 ai 是最小值的的区间就是 (L[i],R[i])​,以 ai 为最小值的贡献就是 ai×(R[i]L[i])

L,R 数组我们可以使用单调栈 O(n) 求解,所以现在的复杂度变成了 O(qn),甚至干掉了最小值线段树

发现这个方法好像不能再优化了,它的局限性在于这个 O(n) ,如果数量少一点,是完全可做的

注意到一个重要性质:|s|105 ,我们考虑根号分治,设闸值 T

  • 对于 ai>T 的部分,这部分的元素数量不超过 |s|T ,我们可以直接采用上述方法,O(q|s|T)
  • 对于 aiT 的部分,我们仿照上述思路,想要令值 val 为所钦定的最小值(注意,不是令某个 ai),我们所可以选取的 ai 都应该满足 aival ,不妨把 aival 的位置 i 标为 1 ,否则标 0 ,那么问题就变成了求 [l,r) 最长连续 1 段长 len,贡献就是 len×val我们可以开 T 棵线段树解决。O(qTlogn)

这样时间复杂度一共是 O(q(|s|T+Tlogn))​ ,当 T=|s|logn 时有最优 O(q(|s|)logn)

小心 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;
} 
posted @   _Famiglistimo  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2021-02-06 [POJ3076] Sudoku
点击右上角即可分享
微信分享提示