序列分治
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\) 单调非严格递减,且所以正好是左右两段。
-
\(rmin_i\ge lmin\):这类情况下,区间 \([l,i]\) 的值为区间和加上 \(lmin\),由于区间和也是单调递增的,所以我们可以二分解决这一段。
-
\(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\) 的关系分成左右两段,双指针维护分界点,与前两题一样。
那么,对于第一类情况,有式子
对于第二类情况,有
公式
都是相当于一段区间的和,搞两棵权值线段树维护两种情况,分界点变化时则将第二类的点取出来放到第一类即可。
时间复杂度 \(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\) 的类别,也就是两个双指针都没走到的位置,我们可以写出式子
其他三类同理。
我们对于每一个右端点的这个值,加入一个数据结构内维护,那么每次查找相当于对一段区间内的查询,当双指针向右移动的时候就把它取出,加入别的类,可以用一个数组来记录当前属于的类别。
同 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;
}

浙公网安备 33010602011771号