CF526F Pudding Monsters 和 CF997E Good Subsegments
Pudding Monsters
给定一个 \(n \times n\) 的棋盘,其中有 \(n\) 个棋子,每行每列恰好有一个棋子。求有多少个 \(k \times k\) 的子棋盘中恰好有 \(k\) 个棋子。
\(n \le 3 \times 10^5\)。
题解
https://www.luogu.com.cn/blog/xht37/solution-cf526f
将二维问题转化为一维问题,即构造一个序列 \(a_{1\dots n}\) ,对于一个点 \((x,y)\) , \(a_x = y\) 。
则原问题转化为经典的连续段计数问题。
本题没有重复元素,那也就是统计 \(\max - \min + 1 = \operatorname{len}\) 的区间个数。
将右端点向右扫,用单调栈维护当前每个后缀的 \(\max\) 和 \(\min\) ,然后用线段树维护当前每个后缀的 \(\max - \min - \operatorname{len}\) 的值,以及每个后缀区间的值的最小值以及最小值的个数。
显然 \(\max - \min - \operatorname{len} \ge -1\) ,同时对于每个右端点 \(r\) ,至少会有一个后缀的值为 \(-1\) ( \(l = r\) 时),因此每个右端点对答案的贡献都是当前后缀区间的值的最小值的个数。
时间复杂度 \(\mathcal O(n \log n)\) 。
下面的代码其实可以处理有重复元素的情况,这时候我们需要统计的就是 \(\max - \min + 1 = \operatorname{cnt}\) 的区间个数,其中 \(\operatorname{cnt}\) 为区间中不同的数的个数,那么记录一下每个数上一次出现的位置即可。
CO int N=3e5+10;
int a[N];
vector<int> mx={0},mn={0};
map<int,int> pos;
int val[4*N],tag[4*N],cnt[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void push_up(int x){
val[x]=min(val[lc],val[rc]);
cnt[x]=(val[lc]==val[x]?cnt[lc]:0)+(val[rc]==val[x]?cnt[rc]:0);
}
IN void put_tag(int x,int v){
val[x]+=v,tag[x]+=v;
}
IN void push_down(int x){
if(tag[x]){
put_tag(lc,tag[x]),put_tag(rc,tag[x]);
tag[x]=0;
}
}
void build(int x,int l,int r){
cnt[x]=r-l+1;
if(l==r) return;
build(lc,l,mid),build(rc,mid+1,r);
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr) return put_tag(x,v);
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr,v);
if(qr>mid) modify(rc,mid+1,r,ql,qr,v);
push_up(x);
}
#undef lc
#undef rc
#undef mid
int main(){
int n=read<int>();
for(int i=1;i<=n;++i) read(a[read<int>()]);
build(1,1,n);
int64 ans=0;
for(int i=1;i<=n;++i){
for(;mx.size()>1 and a[mx.back()]<=a[i];mx.pop_back())
modify(1,1,n,mx[mx.size()-2]+1,mx.back(),-a[mx.back()]);
modify(1,1,n,mx.back()+1,i,a[i]);
mx.push_back(i);
for(;mx.size()>1 and a[mn.back()]>=a[i];mn.pop_back())
modify(1,1,n,mn[mn.size()-2]+1,mn.back(),a[mn.back()]);
modify(1,1,n,mn.back()+1,i,-a[i]);
mn.push_back(i);
modify(1,1,n,pos[a[i]]+1,i,-1);
pos[a[i]]=i;
ans+=cnt[1];
// cerr<<i<<" cnt="<<cnt[1]<<endl;
}
printf("%lld\n",ans);
return 0;
}
Good Subsegments
有一个\(1-n\)的排列\(P\) \((1\le n\le 1.2*10^5)\),如果区间\([l,r]\)中的数是连续的,那么我们称它为好区间。
有\(q\)次询问,每次问\([l,r]\)内,有多少子区间是好的?
\(1\le n,q\le 1.2*10^5\)
题解
https://www.luogu.com.cn/blog/litble-blog/solution-cf997e
所谓好区间,就是 \((\max-\min)-(r-l)=0\) 的区间。
我们将所有询问离线,按照右端点从小到大排序。然后从左到右处理每一个右端点,每次处理时,线段树里维护一下对于每个左端点(这个只需要用单调栈来处理最小值和最大值的更新即可), \((\max-\min)-(r-l)\) 的最小值,那么如果一个区间的最小值为\(0\),最小值个数就是右端点固定,左端点取在这个区间内的时候,好区间的个数。
但是我们要维护的是一个区间内所有子区间,不仅仅是右端点是 \(r\) 的区间。
引入一个新标记\(\text{time}\),表示当前这个最小值个数的贡献,要添加到答案里多少次。我们每次移动右端点的时候,要先把整个线段树的\(\text{time}\)加\(1\),表示还没移动之前的右端点造成的贡献要添加到答案里一次,这样我们就可以维护对于每个左端点,右端点小于等于当前处理的右端点时,好区间个数。那么每次更新询问的答案,只要区间查询即可。
CO int N=1.2e5+10;
int val[4*N],cnt[4*N],tag[4*N],tim[4*N];
int64 sum[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void push_up(int x){
val[x]=min(val[lc],val[rc]);
cnt[x]=(val[lc]==val[x]?cnt[lc]:0)+(val[rc]==val[x]?cnt[rc]:0);
sum[x]=sum[lc]+sum[rc];
}
IN void put_tag(int x,int v){
val[x]+=v,tag[x]+=v;
}
IN void put_tim(int x,int v){
sum[x]+=(int64)cnt[x]*v,tim[x]+=v;
}
IN void push_down(int x){
if(tag[x]){
put_tag(lc,tag[x]),put_tag(rc,tag[x]);
tag[x]=0;
}
if(tim[x]){
if(val[lc]==val[x]) put_tim(lc,tim[x]);
if(val[rc]==val[x]) put_tim(rc,tim[x]);
tim[x]=0;
}
}
void build(int x,int l,int r){
val[x]=l,cnt[x]=1; // offset
if(l==r) return;
build(lc,l,mid),build(rc,mid+1,r);
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr) return put_tag(x,v);
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr,v);
if(qr>mid) modify(rc,mid+1,r,ql,qr,v);
push_up(x);
}
int64 query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return sum[x];
push_down(x);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
int a[N];
vector<int> mx={0},mn={0};
vector<pair<int,int> > q[N];
int64 ans[N];
int main(){
int n=read<int>();
for(int i=1;i<=n;++i) read(a[i]);
int m=read<int>();
for(int i=1;i<=m;++i){
int l=read<int>(),r=read<int>();
q[r].push_back({l,i});
}
build(1,1,n);
for(int i=1;i<=n;++i){
for(;mx.size()>1 and a[mx.back()]<=a[i];mx.pop_back())
modify(1,1,n,mx[mx.size()-2]+1,mx.back(),a[i]-a[mx.back()]);
mx.push_back(i);
for(;mn.size()>1 and a[mn.back()]>=a[i];mn.pop_back())
modify(1,1,n,mn[mn.size()-2]+1,mn.back(),a[mn.back()]-a[i]);
mn.push_back(i);
put_tag(1,-1);
put_tim(1,1);
for(CO pair<int,int>&x:q[i]) ans[x.second]=query(1,1,n,x.first,i);
}
for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
return 0;
}