序列分治

1.序列分治

用分治方法解决序列上的问题,很板的一类题目是求有多少个符合条件的区间。

这类题目的核心思路就是把式子里关于 \(l\) 的部分和关于 \(r\) 的部分拆开来做。

I. ABC282Ex Min + Sum(Diff. 2,412)

Solution:

看到寻找符合条件的区间,不难想到使用分治解决。

按照套路寻找左端点,找有哪些右端点符合条件。

设区间为 \([l,r]\),当前枚举的左端点为 \(lpos\),区间 \([l,mid]\) 的最小值是 \(lmin\),那么所有的右端点 \(i\) 可以按照 \([mid+1,i]\) 的最小值 \(rmin_i\)\(lmin\) 的大小分成两类。

由于 \(rmin_i\) 单调非严格递减,且所以正好是左右两段。

  1. \(rmin_i\ge lmin\):这类情况下,区间 \([l,i]\) 的值为区间和加上 \(lmin\),由于区间和也是单调递增的,所以我们可以二分解决这一段。

  2. \(rmin_i<lmin\):这类情况下区间的值不再具有单调性,我们对 \(b\) 数组求一个前缀和,那么区间和可以表示成 \((pfs[i]-pfs[lpos-1]+rmin_i)\)。我们将与有半部分有关的提取出来,那么剩下的是 \(pfs[lpos-1]\),满足单调递减性质,所以我们可以把所有右端点存入一个数据结构里面,每此更新左端点时把所有已经不满足限制的删去即可。

    不满足限制的情况有两种,一种是当前点在更新左端点后不属于第二类了,另外一种是在算答案贡献的时候已经不满足 \(s\) 的限制了,后面也不可能满足,删去。

    查询则查询值小于等于阈值 \(s-pfs[lpos-1]\) 的个数即可。

    不难想到可以用 std::set 维护。

时间复杂度 \(O(n\log^2n)\)

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int n,m,a[300010],b[300010],ans;
int qzamin[300010],pfs[300010];
#define pr pair<int,int>
#define mp make_pair
void solve(int l,int r){
	if(l==r){
		ans+=((a[l]+b[l])<=m);
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	int lemin=1e18,rxend=mid;
	qzamin[mid]=1e18;
	for(int i=mid+1;i<=r;i++) qzamin[i]=min(qzamin[i-1],a[i]);
	set<pr> s1;
	for(int i=mid+1;i<=r;i++) s1.insert(mp(-qzamin[i]-pfs[i]+pfs[mid],i));
	for(int lpos=mid;lpos>=l;lpos--){
		lemin=min(lemin,a[lpos]);	
		while(rxend<r&&qzamin[rxend+1]>=lemin){
			rxend++;
			auto it=s1.find(mp(-qzamin[rxend]-pfs[rxend]+pfs[mid],rxend));
			if(it!=s1.end()) s1.erase(it);
		}
		int ql=mid+1,qr=rxend,cur=mid;
		while(ql<=qr){
			int md=(ql+qr)>>1;
			if(pfs[md]-pfs[lpos-1]+lemin<=m){
				cur=md,ql=md+1;
			}else qr=md-1;
		}
		ans+=cur-mid;
		while(!s1.empty()){
			pr x=*s1.begin();
			if((-x.first+pfs[mid]-pfs[lpos-1])<=m){
				break;
			}
			s1.erase(s1.begin()); 
		}
		ans+=s1.size();
	}	
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) b[i]=read(),pfs[i]=pfs[i-1]+b[i];
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}

II.CF1156E Special Segments of Permutation (*2,200)

跟上一样的分成两类即可。

时间复杂度 \(O(n \log^2n)\)

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int n,m,a[300010],ans;
int qzamax[300010];
#define pr pair<int,int>
#define mp make_pair
void solve(int l,int r){
	if(l==r){
		ans+=((a[l]+a[l])==a[l]);
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	int lemax=0,rxend=mid;
	qzamax[mid]=0;
	for(int i=mid+1;i<=r;i++) qzamax[i]=max(qzamax[i-1],a[i]);
	multiset<int> s1,s2;
	for(int i=mid+1;i<=r;i++) s2.insert(qzamax[i]-a[i]);
	for(int lpos=mid;lpos>=l;lpos--){
		lemax=max(lemax,a[lpos]);
		while(rxend<r&&qzamax[rxend+1]<lemax){
			rxend++;
			auto it=s2.find(qzamax[rxend]-a[rxend]);
			if(it!=s2.end()){
				s2.erase(it);
				s1.insert((*it)*-1+qzamax[rxend]);
			}
		}
		ans+=s1.count(lemax-a[lpos]);
		ans+=s2.count(a[lpos]); 
		
	}
		
}
signed main(){
	//freopen("1.in","r",stdin);
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}

III.P4755 Beautiful Pair

Solution:

咋还是差不多的式子啊?其实做法略有不一样。

首先按照当前 \(lmax\)\(rmax_i\) 的关系分成左右两段,双指针维护分界点,与前两题一样。

那么,对于第一类情况,有式子

\[\begin{aligned} \\&a_{lpos}\times a_{i}\le lmax \\&a_{i} \le \frac{lmax}{lpos} \end{aligned} \]

对于第二类情况,有

公式

\[\begin{aligned} \\&a_{lpos}\times a_{i}\le rmax_i \\&a_{lpos} \le \frac{rmax_i}{a_i} \end{aligned} \]

都是相当于一段区间的和,搞两棵权值线段树维护两种情况,分界点变化时则将第二类的点取出来放到第一类即可。

时间复杂度 \(O(n\log^2n)\)

由于空间是可以重复运用的,离散化之后直接用普通线段树空间复杂度才是 \(O(n)\) 的,不然的话空间复杂度是 \(O(n \log n \log W)\) 的。

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int n,m,a[300010],ans;
int qzamax[300010];
int tmp[200010],tmp2[200010];
int tr[2][400010];
void pushup(int op,int id){
	tr[op][id]=(tr[op][id<<1]+tr[op][id<<1|1]);
}
void build(int op,int id,int l,int r){
	tr[op][id]=0;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(op,id<<1,l,mid);
	build(op,id<<1|1,mid+1,r);
}
void update(int op,int id,int l,int r,int x,int v){
	if(l==r){
		tr[op][id]+=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(op,id<<1,l,mid,x,v);
	else update(op,id<<1|1,mid+1,r,x,v);
	pushup(op,id);
}
int query(int op,int id,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr){
		return tr[op][id];
	}
	int mid=(l+r)>>1,ans=0;
	if(ql<=mid) ans+=query(op,id<<1,l,mid,ql,qr);
	if(qr>mid) ans+=query(op,id<<1|1,mid+1,r,ql,qr);
	return ans;
}
void solve(int l,int r){
	if(l==r){
		ans+=((a[l]*a[l])<=a[l]);
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	int lemax=0,rxend=mid;
	qzamax[mid]=0;
	for(int i=mid+1;i<=r;i++) qzamax[i]=max(qzamax[i-1],a[i]);
	map<int,int> mp,mp2;
	int idx=0,idx2=0;
	for(int i=mid+1;i<=r;i++){
		tmp[++idx]=a[i];
		tmp2[++idx2]=qzamax[i]/a[i];
	}
	sort(tmp+1,tmp+idx+1);
	sort(tmp2+1,tmp2+idx2+1);
	int sz=unique(tmp+1,tmp+idx+1)-tmp-1;
	int sz2=unique(tmp2+1,tmp2+idx2+1)-tmp2-1;
	for(int i=1;i<=sz;i++) mp[tmp[i]]=i;
	for(int i=1;i<=sz2;i++) mp2[tmp2[i]]=i;
	build(0,1,1,sz2),build(1,1,1,sz);
	for(int i=mid+1;i<=r;i++){
		update(0,1,1,sz2,mp2[qzamax[i]/a[i]],1);
	}
	for(int lpos=mid;lpos>=l;lpos--){
		lemax=max(lemax,a[lpos]);
		while(rxend<r&&qzamax[rxend+1]<lemax){
			rxend++;
			update(0,1,1,sz2,mp2[qzamax[rxend]/a[rxend]],-1);
			update(1,1,1,sz,mp[a[rxend]],1);
		}
		int ql=lower_bound(tmp2+1,tmp2+sz2+1,a[lpos])-tmp2;
		if(ql!=(sz2+1)) ans+=query(0,1,1,sz2,ql,sz2);
		ql=upper_bound(tmp+1,tmp+sz+1,lemax/a[lpos])-tmp-1;
		if(ql) ans+=query(1,1,1,sz,1,ql);
	}
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}

IV.CF526F Pudding Monst (*3,000)

Solution:

巧妙建模。

每行只有一个,可以直接抽象成值, \(n\) 行就变成了长度为 \(n\) 的排列。

相当于问你,给你一个长度为 \(n\) 的排列,问你有多少个区间,满足区间内的极差 \(+1\) 等于区间长度?

\(n\le3\times 10^5\))。

考虑序列分治。

同时维护右边后缀 \(\max\)\(\min\) 两个序列,根据右端点与当前左端点构成的区间里 \(\max\)\(\min\) 的位置将右端点分成四段。

\(\min\)\(\max\) 拆开以后,我们对每一段将式子中跟左端点和右端点有关的值分成两部分,用数据结构维护加入每一个右端点的取值,在左边寻找答案。

本题由于是等式关系,所以我们可以直接用四个 map 存储四类点的值。

当点属于的区间变化之后再将其取出更换到另一个 map 即可。

时间复杂度 \(O(n\log^2n)\),细节见代码。

注意 :std::multiset.count() 的时间复杂度是于答案个数呈线性关系的,用了会被卡成 \(O(n^2)\)

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int n,m,a[300010],ans;
int qzamax[300010],qzamin[300010];
int blt[300010];

int Test; 
void solve(int l,int r){
	if(l==r){
		ans++;
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	
	int lemax=0,rxenda=mid,rxendi=mid,lemin=1e9;
	qzamax[mid]=0,qzamin[mid]=1e9;
	for(int i=mid+1;i<=r;i++) qzamax[i]=max(qzamax[i-1],a[i]),qzamin[i]=min(qzamin[i-1],a[i]);
	map<int,int> s1,s2,s3,s4;//s2:rmin+cr//s3+rmax+cr 
	
	for(int i=mid+1;i<=r;i++){
		s4[qzamax[i]-qzamin[i]-i+mid+1]++;
		blt[i]=4;
	}
	
	for(int lpos=mid;lpos>=l;lpos--){
		
		int cl=(mid-lpos+1);
		
		lemax=max(lemax,a[lpos]);
		lemin=min(lemin,a[lpos]);
		
		while(rxenda<r&&qzamax[rxenda+1]<lemax){
			rxenda++;
			if(blt[rxenda]==4){
				blt[rxenda]=2;
				s4[qzamax[rxenda]-qzamin[rxenda]-rxenda+mid+1]--;
				s2[qzamin[rxenda]+rxenda-mid-1]++;
			}
			
			if(blt[rxenda]==3){
				blt[rxenda]=1;
				s3[qzamax[rxenda]-rxenda+mid+1]--;
				s1[rxenda-mid-1]++;
			}
		}
		
		while(rxendi<r&&qzamin[rxendi+1]>lemin){
			rxendi++;
			if(blt[rxendi]==4){
				blt[rxendi]=3;
				s4[qzamax[rxendi]-qzamin[rxendi]-rxendi+mid+1]--;
				s3[qzamax[rxendi]-rxendi+mid+1]++;
			}
			if(blt[rxendi]==2){
				blt[rxendi]=1;
				s2[qzamin[rxendi]+rxendi-mid-1]--;
				s1[rxendi-mid-1]++;
			}
		}
		
		if(s1.find(lemax-lemin-cl)!=s1.end()) ans+=s1[lemax-lemin-cl];
		if(s2.find(lemax-cl)!=s2.end()) ans+=s2[lemax-cl];
		if(s3.find(lemin+cl)!=s3.end()) ans+=s3[lemin+cl];
		if(s4.find(cl)!=s4.end()) ans+=s4[cl]; 
	}
	
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++){
		int x=read(),y=read();
		a[x]=y;
	}
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}
/*7
1 4
2 3
3 1
4 6
5 2
6 5
7 7
*/

V. ABC248Ex Beautiful Subsequences(Diff. 2,835)

与文章中的 IV.CF526F Pudding Monst 在建模之后式子几乎一样,只是多了个 \(k\),变成了不等式关系 。

以下部分内容摘自第四题。

考虑序列分治。

同时维护右边前缀 \(\max\)\(\min\) 两个序列,枚举左端点 \(lpos\),当前左区间的 \(\max\)\(\min\) 分别为 \(lmax\)\(lmin\),与 [NOIP2022 比赛] 的 \(52\) 分做法类似地根据右端点前缀 \(\max\)\(\min\) 与当前 \(lmax\)\(lmin\) 的位置将右端点分成四段。

设当前考虑的右端点前缀 \(\max\)\(\min\) 分别为 \(rmax\)\(rmin\)

\(\min\)\(\max\) 拆开以后,我们对每一段将式子中跟左端点和右端点有关的值分成两部分,每一类都构成一个不等关系。

如满足 \(lmax \le rmax \land lmin\ge rmin\) 的类别,也就是两个双指针都没走到的位置,我们可以写出式子

\[\begin{aligned} \\rmax-rmin \le (mid-l+1)+(r-mid)+k-1 \\rmax-rmin+(r-mid)\le (mid-l+1)+k-1 \end{aligned} \]

其他三类同理。

我们对于每一个右端点的这个值,加入一个数据结构内维护,那么每次查找相当于对一段区间内的查询,当双指针向右移动的时候就把它取出,加入别的类,可以用一个数组来记录当前属于的类别。

同 III.P4755 Beautiful Pair 的套路,不难想到用离散化后的值域线段树来维护,时间复杂度 \(O(n\ log^2 n)\),空间复杂度 \(O(n)\)

移动右端点时的细节见代码。

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int n,m,a[300010],ans;
int qzamax[300010],qzamin[300010];
int blt[300010];
int tr[5][5700010],k;
// S1   lmax-lmin<=cl+cr+k-1  
// s2   lmax-rmin<=cl+cr+k-1
// S3   rmax-lmin<=cl+cr+k-1
//s4    rmax-rmin<=cl+cr+k-1
//S1   cr+k-1           >=       lmax-lmin-cl
//s2    cr+rmin+k-1       >=     lmax-cl
//s3    rmax-cr-(k-1)     <=    cl+lmin 
//s4     rmax-rmin-cr-(k-1) <=    cl
int tmp[5][300010],idx[5];
void pushup(int op,int id){
	tr[op][id]=(tr[op][id<<1]+tr[op][id<<1|1]);
}
void build(int op,int id,int l,int r){
	tr[op][id]=0;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(op,id<<1,l,mid);
	build(op,id<<1|1,mid+1,r);
}
void update(int op,int id,int l,int r,int x,int v){
	if(l==r){
		tr[op][id]+=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(op,id<<1,l,mid,x,v);
	else update(op,id<<1|1,mid+1,r,x,v);
	pushup(op,id);
}
int query(int op,int id,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr){
		return tr[op][id];
	}
	int mid=(l+r)>>1,res=0;
	if(ql<=mid) res+=query(op,id<<1,l,mid,ql,qr);
	if(qr>mid) res+=query(op,id<<1|1,mid+1,r,ql,qr);
	return res;
}
void solve(int l,int r){
	if(l==r){
		ans+=(0<=r-l+1+k);
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	
	int lemax=0,rxenda=mid,rxendi=mid,lemin=1e9;
	qzamax[mid]=0,qzamin[mid]=1e9;
	for(int i=1;i<=4;i++) idx[i]=0;
	for(int i=mid+1;i<=r;i++) qzamax[i]=max(qzamax[i-1],a[i]),qzamin[i]=min(qzamin[i-1],a[i]);
	for(int i=mid+1;i<=r;i++){
		tmp[1][++idx[1]]=i-mid+k-1;
		tmp[2][++idx[2]]=i-mid+k-1+qzamin[i];
		tmp[3][++idx[3]]=qzamax[i]-(i-mid)-(k-1);
		tmp[4][++idx[4]]=qzamax[i]-qzamin[i]-(i-mid)-(k-1);
		blt[i]=4;
	}
	map<int,int> mp[5];
	for(int i=1;i<=4;i++){
		sort(tmp[i]+1,tmp[i]+idx[i]+1);
		idx[i]=unique(tmp[i]+1,tmp[i]+idx[i]+1)-tmp[i]-1; 
		for(int j=1;j<=idx[i];j++) mp[i][tmp[i][j]]=j;
		build(i,1,1,idx[i]);
	}
	
	for(int i=mid+1;i<=r;i++){
		update(4,1,1,idx[4],mp[4][qzamax[i]-qzamin[i]-(i-mid)-(k-1)],1);
	}
	for(int lpos=mid;lpos>=l;lpos--){
		lemax=max(lemax,a[lpos]);
		lemin=min(lemin,a[lpos]);
		while(rxenda<r&&qzamax[rxenda+1]<lemax){
			rxenda++;
			if(blt[rxenda]==4){
				blt[rxenda]=2;
				update(4,1,1,idx[4],mp[4][qzamax[rxenda]-qzamin[rxenda]-(rxenda-mid)-(k-1)],-1);
				update(2,1,1,idx[2],mp[2][qzamin[rxenda]+(rxenda-mid)+k-1],1);
			}
			if(blt[rxenda]==3){
				blt[rxenda]=1;
				update(3,1,1,idx[3],mp[3][qzamax[rxenda]-(rxenda-mid)-(k-1)],-1);
				update(1,1,1,idx[1],mp[1][(rxenda-mid)+k-1],1);
			}
		}
		while(rxendi<r&&qzamin[rxendi+1]>lemin){
			rxendi++;
			if(blt[rxendi]==4){
				blt[rxendi]=3;
				update(4,1,1,idx[4],mp[4][qzamax[rxendi]-qzamin[rxendi]-(rxendi-mid)-(k-1)],-1);
				update(3,1,1,idx[3],mp[3][qzamax[rxendi]-(rxendi-mid)-(k-1)],1);
			}
			if(blt[rxendi]==2){
				blt[rxendi]=1;
				update(2,1,1,idx[2],mp[2][qzamin[rxendi]+(rxendi-mid)+k-1],-1);
				update(1,1,1,idx[1],mp[1][(rxendi-mid)+k-1],1);
			}
		}
		int cl=(mid-lpos+1);
		int q1=lower_bound(tmp[1]+1,tmp[1]+idx[1]+1,lemax-lemin-cl)-tmp[1];
		if(q1!=idx[1]+1)ans+=query(1,1,1,idx[1],q1,idx[1]);
		
		int q2=lower_bound(tmp[2]+1,tmp[2]+idx[2]+1,lemax-cl)-tmp[2];
		if(q2!=idx[2]+1) ans+=query(2,1,1,idx[2],q2,idx[2]);
		
		int q3=upper_bound(tmp[3]+1,tmp[3]+idx[3]+1,cl+lemin)-tmp[3]-1;
		if(q3) ans+=query(3,1,1,idx[3],1,q3);
		
		int q4=upper_bound(tmp[4]+1,tmp[4]+idx[4]+1,cl)-tmp[4]-1;
		if(q4) ans+=query(4,1,1,idx[4],1,q4);
	}
	
}
signed main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2023-11-09 10:38  ArizonaYYDS  阅读(209)  评论(2)    收藏  举报