CF2009G. Yunli's Subarray Queries 题解

G1

题目要求,对于一个子区间 \(a_{l\sim l+k-1}\),最少要进行多少次单点修改,才能使 \(\forall \ l < i \leq l+k-1,a_i=a_{i-1}+1\),其中 \(k\) 是固定的。

对于这种问题,我们通常有一个 trick:将 \(a_i\) 变为 \(a_i-i\)。这样的话,我们要求的答案就变为了 \(k\) 减去变化后的 \(a_{l\sim l+k-1}\) 中众数的出现次数。这个可以用线段树或者莫队做。

以下是线段树的代码,为了避免出现 \(a_i < 0\),这里将 \(a_i\) 变为了 \(a_i-i+n\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int T,n,m,q,a[200005],ans[200005];

namespace SGT{
	int t[400005<<2];
	inline void pushup(int k){
		t[k]=max(t[k<<1],t[k<<1|1]);return;	
	}
	inline void build(int k=1,int l=1,int r=2*n){
		if(l==r){
			t[k]=0;
		}else{
			int mid=l+r>>1;
			build(k<<1,l,mid),build(k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
	inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
		if(P<=l&&r<=P){
			t[k]+=val;
		}else{
			int mid=l+r>>1;
			if(P<=mid)updata(P,val,k<<1,l,mid);
			else updata(P,val,k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
}

inline int read(){
	register int x(0),t(0);
	static char ch=getchar();
	while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
	return t?-x:x;
}

signed main(){
	T=read();while(T--){
	
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++){
		a[i]=read()+n-i;
	}
	SGT::build();
	for(int i=1;i<=m;i++){
		SGT::updata(a[i],1);
	}
	ans[1]=m-SGT::t[1];
	for(int i=2;i+m-1<=n;i++){
		SGT::updata(a[i-1],-1);
		SGT::updata(a[i+m-1],1);
		ans[i]=m-SGT::t[1];
	}
	for(int i=1;i<=q;i++){
		int l=read(),r=read();
		cout<<ans[l]<<'\n';
	}
	
    }return 0;
}

时间复杂度为 \(O(n\times\log n)\)

G2

我们先用 G1 的方法求出 \(f(\{a_i,a_{i+1},\dots, a_{i+k-1}\})\),并设其为 \(w_i\)

与 G1 不同,G2 要求的的是 \(\sum \limits_{i=l}^{r-k+1} \min \limits_{j=l}^{i} w_j\),其中 \(r \geq l+k-1\)

看到对一段前缀取最小值,我们考虑如何模拟出这一过程。设 \(p_0=l\),每次往后找到离 \(p_0\) 最近的满足 \(w_{p_0} > w_p\)\(p\),此时 \(\min \limits_{j=l}^{i} w_j=w_{p_0}(p_0\le i\le p - 1)\),这段对答案的贡献为 \((p-p_0)\times w_{p_0}\) ,再将 \(p_0\) 设为 \(p\)。不断这样做下去,直到 \(p_0\) 后已经没有 \(w_{p_0}>w_p\) 或者 \(p_0>r-k+1\)

我们不可能每次询问都这样暴力做一次,考虑如何优化。显然,对于每个 \(p_0\),其后最近的满足 \(w_{p_0} > w_p\)\(p\) 都是不变的。考虑提前将每个 \(p_0\)\(p\) 求出来,这个可以用单调栈求。然后我们需要用到另一个 trick:将 \(p\)\(p_0\) 连一条长度为 \((p-p_0)\times w_{p_0}\) 的边,这样连出来一定是棵树。(我也不太清楚这个 trick 的名字,可能跟 AC 自动机的优化差不多。)

这样我们可以在树上二分出最后一个不大于 \(r-k+1\)\(p_0\)。具体实现:先一遍 dfs 将 \(dep_i\)\(dis_i\) 求出,其中 \(dp_i\) 表示 \(i\) 的深度,\(dis_i\) 表示 \(i\) 到根节点(为了方便设为 \(n-m+2\))的距离。接着将二分的左起始点设为 \(1\),右起始点设为 \(dep_l\),判断 \(l\) 在树上的 \(dep_l-mid\) 级祖先是否小于等于 \(r-k+1\),移动 \(l\)\(r\) 进行二分,设二分出来的答案为 \(res\)。这时答案就为 \(dis_l-dis_{res}+(r-k-res+2)\times w_{res}\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int T,n,m,q,a[200005],w[200005];

vector <int> to[200005];

int st[200005],top;

namespace SGT{
	int t[400005<<2];
	inline void pushup(int k){
		t[k]=max(t[k<<1],t[k<<1|1]);return;	
	}
	inline void build(int k=1,int l=1,int r=2*n){
		if(l==r){
			t[k]=0;
		}else{
			int mid=l+r>>1;
			build(k<<1,l,mid),build(k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
	inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
		if(P<=l&&r<=P){
			t[k]+=val;
		}else{
			int mid=l+r>>1;
			if(P<=mid)updata(P,val,k<<1,l,mid);
			else updata(P,val,k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
}

namespace SP{
	#define Rana_Love true
	int siz[200005],dfn[200005],dep[200005],fa[200005],top[200005],mxs[200005],fp[200005],dis[200005],cnt;
	inline void dfs1(int x,int last){
		dep[x]=dep[last]+1,fa[x]=last,siz[x]=1;
		for(int y:to[x]){
			if(y==last)continue;
			dis[y]=dis[x]+(x-y)*w[y];
			dfs1(y,x),siz[x]+=siz[y];
			if(siz[y]>siz[mxs[x]])mxs[x]=y;
		}return;
	}
	inline void dfs2(int x,int last){
		dfn[x]=++cnt,fp[cnt]=x,top[x]=last;
		if(!mxs[x])return;dfs2(mxs[x],last);
		for(int y:to[x]){
			if(y==fa[x]||y==mxs[x])continue;
			dfs2(y,y);
		}return;
	}
	inline void init(){
		cnt=0;for(int i=1;i<=n-m+2;i++)mxs[i]=0;dfs1(n-m+2,0),dfs2(n-m+2,n-m+2);return;
	}
	inline int jump(int x,int deep){
		int step=dep[x]-deep;
		while(Rana_Love){	
			if(dep[x]-dep[top[x]]+1<=step){
				step+=dep[top[x]]-dep[x]-1,x=fa[top[x]];
			}else break;
		}return fp[dfn[x]-step];
	}
	inline int solve(int st,int ed){
		int l=1,r=dep[st];
		while(l<r){
			int mid=l+r>>1;
			if(jump(st,mid)<=ed-m+1){
				r=mid;
			}else l=mid+1;
		}r=jump(st,r);
		return dis[st]-dis[r]+w[r]*(ed-m+1-r+1);
	}
}

inline int read(){
	register int x(0),t(0);
	static char ch=getchar();
	while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
	return t?-x:x;
}

signed main(){
	T=read();while(T--){
	
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++){
		a[i]=read()+n-i;
	}
	for(int i=1;i<=n-m+2;i++){
		to[i].clear();
	}
	SGT::build();
	for(int i=1;i<=m;i++){
		SGT::updata(a[i],1);
	}w[1]=m-SGT::t[1];
	for(int i=2;i+m-1<=n;i++){
		SGT::updata(a[i-1],-1);
		SGT::updata(a[i+m-1],1);
		w[i]=m-SGT::t[1];
	}
	st[top=0]=n-m+2;
	for(int i=n-m+1;i>=1;i--){
		while(top&&w[st[top]]>=w[i])top--;
		to[st[top]].push_back(i),st[++top]=i;
	}
	SP::init();
	while(q--){
		int l=read(),r=read();
		cout<<SP::solve(l,r)<<'\n';
	}
	
    }return 0;
}

我的树上 k 级祖先是用重链剖分求的,因此时间复杂度为 \(O(n\times \log^2 n)\)。可以将重链剖分换成长链剖分之类的,时间复杂度可以降到 \(O(n\times\log n)\)。或许可以直接上历史和线段树?

G3

G3要求的东西变为了 \(\sum \limits_{i=l}^{r-k+1} \sum \limits_{j=i}^{r-k+1} \min \limits_{g=i}^{j} w_g\)。类似这种式子套上 P3246 [HNOI2016] 序列 的方法就行了。不知道能否将G2的方法继续扩展到G3,我还没想出来,如果可以的话还请告诉我!

这里仅仅贴一份用莫队实现的代码,具体思路参考 P3246 [HNOI2016] 序列 的题解:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int lim=200000,block=447;

int lg[200005];

int T,n,m,len,a[200005],w[200005],ans[200005];

namespace SGT{
	int t[400005<<2];
	inline void pushup(int k){
		t[k]=max(t[k<<1],t[k<<1|1]);return;	
	}
	inline void build(int k=1,int l=1,int r=2*n){
		if(l==r){
			t[k]=0;
		}else{
			int mid=l+r>>1;
			build(k<<1,l,mid),build(k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
	inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
		if(P<=l&&r<=P){
			t[k]+=val;
		}else{
			int mid=l+r>>1;
			if(P<=mid)updata(P,val,k<<1,l,mid);
			else updata(P,val,k<<1|1,mid+1,r);
			pushup(k);
		}return;
	}
}

int dp[2][200005];

namespace ST{
	int f[200005][25],pre[200005],nxt[200005],st[200005],top;
	inline int comp(int A,int B){
		return w[A]<w[B]?A:B;
	}
	inline void init(){
		for(int i=1;i<=n-len+1;i++){
			f[i][0]=i;
		}
		for(int j=1;1<<j<=n-len+1;j++){
			for(int i=1;i+(1<<j)-1<=n-len+1;i++){
				f[i][j]=comp(f[i][j-1],f[i+(1<<j-1)][j-1]);
			}
		}
		dp[0][st[top=0]=0]=0;
		for(int i=1;i<=n-len+1;i++){
			while(top&&w[st[top]]>=w[i])top--;
			pre[i]=st[top],st[++top]=i;
		}
		for(int i=1;i<=n;i++){
			dp[0][i]=dp[0][pre[i]]+(i-pre[i])*w[i];
		}
		dp[1][st[top=0]=n-len+2]=0;
		for(int i=n-len+1;i>=1;i--){
			while(top&&w[st[top]]>=w[i])top--;
			nxt[i]=st[top],st[++top]=i;
		}
		for(int i=n-len+1;i>=1;i--){
			dp[1][i]=dp[1][nxt[i]]+(nxt[i]-i)*w[i];
		}
		return;
	}
	inline int query(int l,int r){
		return comp(f[l][lg[r-l+1]],f[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
	}
}

int lt,rt,res;

struct Rana{
	int l,r,id;
}q[200005];

inline int idx(int x){return (x-1)/block+1;}

inline bool cmp(Rana A,Rana B){
	return idx(A.l)!=idx(B.l)?idx(A.l)<idx(B.l):idx(A.r)<idx(B.r);
}

inline int left(int l,int r){
	int pos=ST::query(l,r);
	return w[pos]*(r-pos+1)+dp[1][l]-dp[1][pos];
}

inline int right(int l,int r){
	int pos=ST::query(l,r);
	return w[pos]*(pos-l+1)+dp[0][r]-dp[0][pos];
}

inline int read(){
	register int x(0),t(0);
	static char ch=getchar();
	while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
	return t?-x:x;
}

signed main(){
	for(int i=2;i<=lim;i++){
		lg[i]=lg[i>>1]+1;
	}T=read();while(T--){
	
	n=read(),len=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read()-i+n;
	}
	SGT::build();
	for(int i=1;i<=len;i++){
		SGT::updata(a[i],1);
	}w[1]=len-SGT::t[1];
	for(int i=2;i+len-1<=n;i++){
		SGT::updata(a[i-1],-1);
		SGT::updata(a[i+len-1],1);
		w[i]=len-SGT::t[1];
	}
	ST::init();
	for(int i=1;i<=m;i++){
		q[i]={read(),read()-len+1,i};
	}
	sort(q+1,q+1+m,cmp),lt=1,rt=0,res=0;
	for(int i=1;i<=m;i++){
		while(lt>q[i].l)res+=left(--lt,rt);
		while(rt<q[i].r)res+=right(lt,++rt);
		while(lt<q[i].l)res-=left(lt++,rt);
		while(rt>q[i].r)res-=right(lt,rt--);
		ans[q[i].id]=res;
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	
	}return 0;
}
posted @   PNNNN  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示