2018宁夏邀请赛 Continuous Intervals(线段树+单调栈,好题)

题意:题目大意:给定一个长度为 N 的序列,定义连续区间 [l, r] 为:序列的一段子区间,满足 [l, r] 中的元素从小到大排序后,任意相邻两项的差值不超过1。求一共有多少个连续区间。

题解:某一个连续区间等价于max-min+1=cnt,
cnt为区间内不同的数的个数,我们变形一下可以得到max-min-cnt==-1,所以我们固定当前的区间右端点R,用线段树维护在(1,R)区间内有多少个区间满足max-min-cnt==-1即可。线段树内维护的是一个区间内的各个子区间内满足max-min-cnt==-1的个数。

注意这里我们维护max 和 min去不断更新区间的时候,我看很多博客直接就去说用单调栈维护就行了,这里我想了好长时间。之所以能用单调栈做是因为对于一个单调栈内相邻的两个元素w[a],w[b],(a,b表示下标),在[a+1,b]范围内w[b]一定是最值,所以对于每个单调栈中我们就可以在单调栈更新的过程中去更新相邻元素的这个区间段,这样的区间段一定是不重不漏的,这样扫描的时间复杂度就从N^2降到了N。第一次见单调栈这样优化,学到了。

AC代码:

#include <iostream>
#include <cstring>
#include <map>
#include <algorithm>
#define int long long
#define p first
#define k second
using namespace std;
typedef pair<int,int> PII;
const int N=1e6+5;
int n;
int w[N];
PII smin[N],smax[N];
 
struct node{
	int l,r;
	int m,sum,add;//m表示区间内max-min-cnt的最小值 
}tr[N*2];
 
void pushup(int u){
	auto l=tr[u<<1],r=tr[u<<1|1];
	tr[u].m=min(l.m,r.m);
	
	if(l.m>r.m) tr[u].sum=r.sum;
	if(l.m<r.m) tr[u].sum=l.sum;
	if(l.m==r.m) tr[u].sum=l.sum+r.sum;
}
 
void pushdown(int u){
	if(tr[u].add){
		auto &l=tr[u<<1],&r=tr[u<<1|1];
		auto add=tr[u].add;
		l.add+=add,l.m+=add;
		r.add+=add,r.m+=add;
		tr[u].add=0;
	}
}
 
void build(int u,int l,int r){
	tr[u]={l,r,0,0,0};
	if(l==r){
		tr[u].sum=1;
		return;
	}
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
 
void update(int u,int l,int r,int v){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].add+=v;
		tr[u].m+=v;
		return;
	}
	
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid)update(u<<1,l,r,v);
	if(r>mid)update(u<<1|1,l,r,v);
	pushup(u);
}
 
int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){
		if(tr[u].m==-1)return tr[u].sum;
		return 0;
	}
	
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1,res=0;
	if(l<=mid) res+=query(u<<1,l,r);
	if(r>mid) res+=query(u<<1|1,l,r);
	
	return res;
}
 
main(){
	int T,Case=0;
	cin>>T;
	while(T--){
		scanf("%lld",&n);
		
		build(1,1,n);
		
		map<int,int>mp;
		
		int res=0,t1=0,t2=0;
		smax[0]={0,0},smin[0]={0,0}; 
		for(int i=1;i<=n;i++){
			int k;
			scanf("%lld",&k);
			
			while(t1&&smax[t1].k<k){
				int v=k-smax[t1].k;
				int l=smax[t1-1].p+1,r=smax[t1].p;
				t1--;
				update(1,l,r,v);//更新max改变对max-min-cnt照成的影响 
			}
			smax[++t1]={i,k};
			
			while(t2&&smin[t2].k>k){
				int v=smin[t2].k-k;
				int l=smin[t2-1].p+1,r=smin[t2].p;
				t2--;
				update(1,l,r,v);//更新min改变对max-min-cnt照成的影响 
			}
			smin[++t2]={i,k};
			
			update(1,mp[k]+1,i,-1);//更新cnt改变对max-min-cnt照成的影响 
			mp[k]=i;
			
			res+=query(1,1,i);	
		}
		
		printf("Case #%lld: %lld\n",++Case,res);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值