CF526F Pudding Monsters 【分治】

题目

题目链接

题意

\(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<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\n",ans);
	return 0;
}
posted @ 2020-07-11 19:44  Vocanda  阅读(173)  评论(2编辑  收藏  举报