• CF526F Pudding Monsters 【分治】


    题目

    题目链接

    题意

    (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;
    }
    
  • 相关阅读:
    精选css动画库及其使用
    使用js reduce方法求数组中出现次数最多的元素
    文字横向滚动效果,公告效果
    判断是否是微信端
    移动端input/textarea输入框光标高度兼容及其他事项
    更新被拒绝,因为您当前分支的最新提交落后于其对应的远程分支
    Git 常见问题整理
    CentOS 7.0 安装配置LAMP服务器方法(Apache+PHP+MariaDB)
    centos7安装eclipse
    centos7安装mplayer的方法
  • 原文地址:https://www.cnblogs.com/Vocanda/p/13285189.html
Copyright © 2020-2023  润新知