AtCoder Beginner Contest 248 H

https://atcoder.jp/contests/abc248/tasks/abc248_h
官方题解使用的是线段树,不过分治可能更简单一些。

我们调用divide(l,r),表示区间[l,r]的合法子序列个数。

根据分治的套路:divide(l,r)=divide(l,mid)+divide(mid+1,r)+f,其中f表示跨越左右两个侧的合法子序列个数。(这里和下面“左侧”指的是区间[l,mid],“右侧”指的是区间[mid+1,r]

问题是如何求f

分为下面四种情况:

  1. 最大、最小值都在左侧
  2. 最大、最小值都在右侧
  3. 最大值在左侧,最小值在右侧
  4. 最大值在右侧,最小值在左侧

1,2两种情况类似,我们就讨论情况1,另一种程序是差不多的。

若最大、最小值都在左侧,那么我们枚举L,表示合法区间的左端点,枚举K,表示当前k的大小,那么:maxmin=RL+K,可以变形成:R=LK+maxmin。而最小、最大值都在左侧,故我们可以知道maxmin。接下来我们要判断R是否合法。首先mid<R<=r,其次mini=mid+1Rpi>mini=Lmidpimaxi=mid+1Rpi<maxi=Lmidpi,否则最大或最小值就不在左侧了。

预处理min,max,时间复杂度为O(nk)

3,4两种情况,我们只讨论4,另一种程序是差不多的。

我们继续枚举L,意义同上,此时必须满足:

mini=Lmidpi<mini=mid+1Rpimaxi=Lmidpi<maxi=mid+1Rpi

此时,我们发现,若L从大往小枚举(即从mid枚举到l),那么mini=Lmidpi越来越小,maxi=Lmidpi越来越大。那么我们可以使用单调队列维护合法的R的位置。

那么如何进行统计呢?我们也是枚举K,此时min,L,K都是已知的,而R,max是未知的,那么:

maxmin=RL+KmaxR=minL+K

故我们维护一个桶cnt,单调队列加入一个元素时,cnt[maxR]++,删除时cnt[maxR],查询时只要查cnt[minL+K]的大小即可。

注意到maxR有可能小于0,故我们要将所有下标加上n

时间复杂度也是O(nk)

故,一次分治的复杂度为O(nk),一共logn层,总的时间复杂度为O(nklogn)

代码如下:

#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl

using ll=long long;

const int maxn=150005;
int n,k;
int a[maxn],mx[maxn],mn[maxn],cnt[maxn+maxn];

ll divide(int l,int r) {
	if(l==r) return 1ll;
	int m=l+r>>1; ll ret=divide(l,m)+divide(m+1,r);
	mx[m]=mn[m]=a[m],mx[m+1]=mn[m+1]=a[m+1];
	for(int i=m-1;i>=l;i--) {
		mx[i]=std::max(mx[i+1],a[i]);
		mn[i]=std::min(mn[i+1],a[i]);
	}
	for(int i=m+2;i<=r;i++) {
		mx[i]=std::max(mx[i-1],a[i]);
		mn[i]=std::min(mn[i-1],a[i]);
	}
	for(int L=m;L>=l;L--)
		for(int K=0;K<=k;K++) {
			int R=mx[L]-mn[L]+L-K;
			if(m<R&&R<=r&&mx[R]<mx[L]&&mn[R]>mn[L]) ret++;//注意判断R的条件不能漏
		}
	for(int R=m+1;R<=r;R++)
		for(int K=0;K<=k;K++) {
			int L=R+K-mx[R]+mn[R];
			if(l<=L&&L<=m&&mx[L]<mx[R]&&mn[L]>mn[R]) ret++;
		}
	int R1=m+1,R2=m+1;
	for(int L=m;L>=l;L--) {
		while(R2<=r&&mn[R2]>mn[L]) cnt[mx[R2]-R2+n]++,R2++;//注意:单调队列必须先加后删
		while(R1<R2&&mx[R1]<mx[L]) cnt[mx[R1]-R1+n]--,R1++;
		for(int K=0;K<=k;K++) ret+=cnt[mn[L]-L+K+n];
	}
	while(R1<R2) cnt[mx[R1]-R1+n]--,R1++;//清空cnt 
	int L1=m,L2=m;
	for(int R=m+1;R<=r;R++) {
		while(L2>=l&&mn[L2]>mn[R]) cnt[mx[L2]+L2]++,L2--;
		while(L1>L2&&mx[L1]<mx[R]) cnt[mx[L1]+L1]--,L1--;
		for(int K=0;K<=k;K++) ret+=cnt[R+K+mn[R]];
	}
	while(L1>L2) cnt[mx[L1]+L1]--,L1--;
	return ret;
}

int main() {
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	printf("%lld\n",divide(1,n));
	return 0;
}
posted @   Nastia  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示