【CF526F】Pudding Monsters(分治套路题)
- 一个\(n\times n\)的正方形,其中每行每列恰有一个点。
- 问有多少子正方形,也满足每行每列恰有一个点。
- \(n\le3\times10^5\)
二维化一维
显然这道题很容易想到降维。
既然每行只有一个点,我们可以用\(a_i\)表示第\(i\)行的点在第几列。
现在问题就变成有多少区间满足其中元素排序之后值连续。
则区间\([l,r]\)合法的充要条件就是\(\max_{i=l}^ra_i-\min_{i=l}^ra_i=r-l\)。
分治套路题
对于当前分治区间\([l,r]\),设其中点为\(mid\),那么\([l,mid]\)和\([mid+1,r]\)两区间各自的答案可以直接递归处理。
接着我们先预处理出\(Mx,Mn\)两个数组,当\(i\in[l,mid]\)时表示\([i,mid]\)的最值,当\(i\in[mid+1,r]\)时表示\([mid+1,i]\)的最值。
现在我们就要考虑如何统计跨中点的区间的答案,分成两类情况。
两最值在中点同侧
假设在\([l,mid]\)中。
我们枚举左端点\(i\),此时最值为\(Mx_i,Mn_i\),所以满足\(Mx_i-Mn_i=j-i\),即\(j=Mx_i-Mn_i+i\)。
那么我们只要验证一下\(j\)是不是一个合法的右端点即可。
两最值在中点异侧
假设最小值在\([l,mid]\)中,最大值在\([mid+1,r]\)中。
我们从大到小枚举\(i\),同时开\(p,q\)两个指针维护出区间\([p,q]\)表示合法右端点\(j\)的取值区间,显然这东西随着\(i\)的移动是单调右移的。
此时最值为\(Mx_j,Mn_i\),故\(Mx_j-Mn_i=j-i\),即\(Mn_i-i=Mx_j-j\)。
那么只要开桶记一下\(Mx_j-j=x\)的\(j\)的个数,就可以了。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300000
#define LL long long
using namespace std;
int n,a[N+5];LL ans;
int Mx[N+5],Mn[N+5],c[2*N+5];I void Solve(CI l,CI r)//分治
{
if(l==r) return (void)++ans;RI i,p,q,mid=l+r>>1;Solve(l,mid),Solve(mid+1,r);//递归子区间
Mx[mid]=Mn[mid]=a[mid],Mx[mid+1]=Mn[mid+1]=a[mid+1];
for(i=mid-1;i>=l;--i) Mx[i]=max(Mx[i+1],a[i]),Mn[i]=min(Mn[i+1],a[i]);//预处理左区间后缀最值
for(i=mid+2;i<=r;++i) Mx[i]=max(Mx[i-1],a[i]),Mn[i]=min(Mn[i-1],a[i]);//预处理右区间前缀最值
for(i=l;i<=mid;++i) p=i+Mx[i]-Mn[i],mid<p&&p<=r&&Mn[i]<Mn[p]&&Mx[p]<Mx[i]&&++ans;//最值都在左区间
for(i=mid+1;i<=r;++i) p=i-Mx[i]+Mn[i],l<=p&&p<=mid&&Mn[i]<Mn[p]&&Mx[p]<Mx[i]&&++ans;//最值都在右区间
for(i=mid,p=q=mid+1;i>=l;ans+=c[Mn[i]-i+n],--i)//最小值在左区间,最大值在右区间
{W(q<=r&&Mn[q]>Mn[i]) ++c[Mx[q]-q+n],++q;W(p^q&&Mx[p]<Mx[i]) --c[Mx[p]-p+n],++p;}//p,q维护合法右端点区间
W(p^q) --c[Mx[p]-p+n],++p;//清空
for(i=mid,p=q=mid+1;i>=l;ans+=c[Mx[i]+i],--i)//最大值在左区间,最小值在右区间
{W(q<=r&&Mx[q]<Mx[i]) ++c[Mn[q]+q],++q;W(p^q&&Mn[p]>Mn[i]) --c[Mn[p]+p],++p;}
W(p^q) --c[Mn[p]+p],++p;//清空
}
int main()
{
RI i,x,y;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&x,&y),a[x]=y;//二维化一维
return Solve(1,n),printf("%lld\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒