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;
} 
posted @ 2022-02-06 23:56  _Famiglistimo  阅读(40)  评论(0编辑  收藏  举报