题目
题目链接
题意
\(1\)、一个\(n\times n\)的矩阵里每行每列都只有一个\(Pudding\ Monsters\)
\(2\)、找\(k\times k\)的矩阵满足其中有\(k\)个\(Pudding\ Monsters\)
\(3\)、\(n\le 3\times 10^5,1\le k\le n\)
分析
分治思想就是把一段长区间上的问题分到一个个子问题来进行解决,最后分别统计答案。
这个题因为给出的是一个二维平面,所以我们需要转化为一维。题目中有一个很重要的性质:每一行和每一列都只有一个点。
所以我们就可以直接把当前列的\(Pudding Monsters\)在哪一行记录下来,我们就得到了一个序列,然后开始分治解决。我们的问题就简化为了有多少区间\([l,r]\)使\(Max-Min=l-r\)成立,其中\(Max,Min\)分别为区间\([l,r]\)的最大和最小值。
我们把每一个区间分成两部分\([l,mid],[mid+1,r]\),分成一个个的子问题来解决。所以我们在当前情况下只需要考虑跨过\(mid\)的情况就可以了,其他的都是递归的子问题。
我们需要先预处理最大和最小值。设\(mx[i]\)和\(mn[i]\)为最大和最小数组,那么\(l\le i\le mid\)时,这两个数组表示的是从\(i\)到\(mid\)的最大和最小,而\(mid+1\le i\le r\)时两个数组表示从\(mid+1\)到\(i\)的最大和最小。
单个点我们先不必考虑,因为单个点就直接让答案\(ans++\)就行了,表示一个\(1\times 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\to mn[i]-i=mx[j]-j\)
\(2\)、与上边相反,最小值在右,最大在左,那么我们枚举右端点\(i\),则得到\(mx[j]-mn[i] = i-j\to 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\to mn[i]-i=mx[j]-j\)当作下标,如果满足条件就让下标为\(mx[j]-j\)的那个位置的答案加一。拿情况\(1\)举例,设记录数组为\(jl[i]\),如果右边枚举到的\(j\)的最小大于\(mn[i]\),那么就让\(jl[mx[j]-j+n]++\)其中加\(n\)是为了避免出现负的下标。而我们定义的另一个指针\(k\)是用来善后的,也就是说在\(k
代码
#include
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]=l && mx[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=l && mn[j]>mn[i]){
jl[mx[j]+j]++;
--j;
}
while(k>j && mx[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\n",ans);
return 0;
}