题目
题意
(1)、一个(n imes n)的矩阵里每行每列都只有一个(Pudding Monsters)
(2)、找(k imes k)的矩阵满足其中有(k)个(Pudding Monsters)
(3)、(nle 3 imes 10^5,1le kle n)
分析
分治思想就是把一段长区间上的问题分到一个个子问题来进行解决,最后分别统计答案。
这个题因为给出的是一个二维平面,所以我们需要转化为一维。题目中有一个很重要的性质:每一行和每一列都只有一个点。
所以我们就可以直接把当前列的(Pudding Monsters)在哪一行记录下来,我们就得到了一个序列,然后开始分治解决。我们的问题就简化为了有多少区间([l,r])使(Max-Min=l-r)成立,其中(Max,Min)分别为区间([l,r])的最大和最小值。
我们把每一个区间分成两部分([l,mid],[mid+1,r]),分成一个个的子问题来解决。所以我们在当前情况下只需要考虑跨过(mid)的情况就可以了,其他的都是递归的子问题。
我们需要先预处理最大和最小值。设(mx[i])和(mn[i])为最大和最小数组,那么(lle ile mid)时,这两个数组表示的是从(i)到(mid)的最大和最小,而(mid+1le ile r)时两个数组表示从(mid+1)到(i)的最大和最小。
单个点我们先不必考虑,因为单个点就直接让答案(ans++)就行了,表示一个(1 imes 1)的合法状态。
下面我们来分析两个分开的区间合并的问题,那么一共有四种情况:
(1)、最大最小都在左区间,上面已经分析过,如果有区间([l,r])使(Max-Min=l-r)成立,那么就是一个情况。所以我们枚举左端点(i),那么根据上式得到(j=i+mx[i]-mn[i])。
(2)、最大最小都在右区间,那么枚举右端点(i),则左端点(j=i-(mx[i]-mn[i]))。
以上两种情况都很好分析,确定了一个端点,那么另一个端点就确定了,我们直接(ans++)就行了。
下边是两种比较复杂的情况:
(1)、最小值在左,最大在右,那么我们就可以根据上边说到的那个式子得出来枚举左端点(i),那么右端点和其的关系就是(mx[j]-mn[i] = j-i o mn[i]-i=mx[j]-j)
(2)、与上边相反,最小值在右,最大在左,那么我们枚举右端点(i),则得到(mx[j]-mn[i] = i-j o mn[i]+i=mx[j]+j)
这两种情况都是一端确定,但是另一端无法确定,以复杂情况(1)为例,左小右大,那么枚举左端点,右端点并不确定,所以我们每一次向右扫描,(mx[j])都是会变的,但是不用每一个(i)都扫描一遍(j)。因为当 (i--) 的时候,如果左侧增加的这个数更新了最小值,则上一轮已经扫描过 (mid+1∼j) 仍然满足 (mn[j]>mn[i]) 。如果增加的是一个较大的数且大于了 (mx[j]) 此时我们也只需往后扫描找到当前的满足条件的 (j) 即可。
具体实现方式就是我们定义两个指针位置,(j)和(k)都代表右边的点,我们用上边说到的条件(mx[j]-mn[i] = j-i o mn[i]-i=mx[j]-j)当作下标,如果满足条件就让下标为(mx[j]-j)的那个位置的答案加一。拿情况(1)举例,设记录数组为(jl[i]),如果右边枚举到的(j)的最小大于(mn[i]),那么就让(jl[mx[j]-j+n]++)其中加(n)是为了避免出现负的下标。而我们定义的另一个指针(k)是用来善后的,也就是说在(k<j)的情况下,如果(mx[k])比左边的(mx[i])还小,那么我们让其对应的下标的(jl[mx[k]-k+n]--)。因为一开始我们记录答案用的是(mx[j]-j+n),所以最后只需要按照当前状况下的满足条件(mx[j]-j=mn[i]-i)让下标变成(mn[i]-i+n)来记录,这样我们得到的就都是合法情况了,第二种情况和这个是一样的,但是要注意的是每一次都要清空之前的数组,避免出现问题。具体一些会在代码里注释。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+10;
int mn[maxn],mx[maxn],jl[maxn<<1];
ll ans;
int n,a[maxn];
void fenz(int l,int r){
if(l == r){//统计1×1的情况
ans++;
return;
}
int mid = (l+r)>>1;
fenz(l,mid);//递归处理子序列
fenz(mid+1,r);
mx[mid] = mn[mid] = a[mid];//初始化
mx[mid+1] = mn[mid+1] = a[mid+1];
for(int i=mid-1;i>=l;--i){//初始化每个左区间端点的最大最小值
mx[i] = max(mx[i+1],a[i]);
mn[i] = min(mn[i+1],a[i]);
}
for(int i=mid+2;i<=r;++i){//初始化每个右区间端点的最大最小值
mx[i] = max(mx[i-1],a[i]);
mn[i] = min(mn[i-1],a[i]);
}
for(int i=mid;i>=l;--i){//最大最小都在左边
int j=mx[i]-mn[i]+i;//直接按照条件进行
if(j<=r && j>mid && mx[j]<mx[i] && mn[i]<mn[j])ans++;
}
for(int i=mid+1;i<=r;++i){//都在右边
int j=i-mx[i]+mn[i];
if(j<=mid && j>=l && mx[j]<mx[i] && mn[j]>mn[i])
ans++;
}
int j=mid+1,k=mid+1;
for(int i=mid;i>=l;--i){//左小右大,mn[i]-i=mx[j]-j
while(j<=r && mn[j]>mn[i]){//右边最小比左边最小大
jl[mx[j]-j+n]++;//条件作为下标让jl数组++
j++;//继续向后枚举
}
while(k<j && mx[k]<mx[i]){//善后,如果右边最大比左边还小,不符合
jl[mx[k]-k+n]--;//直接让当前条件--
k++;
}
ans+=(ll)jl[mn[i]-i+n];//最后用条件的另一边统计答案,这样统计到的就都是满足条件的答案
}
while(k<j){//清空,不要用memset,会TLE
jl[mx[k]-k+n]--;
k++;
}
j=mid;
k=mid;
for(int i=mid+1;i<=r;++i){//左大右小,以下具体与上边一样
while(j>=l && mn[j]>mn[i]){
jl[mx[j]+j]++;
--j;
}
while(k>j && mx[k]<mx[i]){
jl[mx[k]+k]--;
k--;
}
ans+=(ll)jl[mn[i]+i];
}
while(k>j){//再清空
jl[mx[k]+k]--;
k--;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
int x,y;
scanf("%d%d",&x,&y);
a[x] = y;//将二维平面转到一个序列上
}
fenz(1,n);
printf("%lld
",ans);
return 0;
}