【XSY3367】青春野狼不做姐控偶像的梦(线段树)

题意:给一个 1 ∼ n 1\sim n 1n 的排列,多次询问某段区间内的值域连续子区间的个数。

区间值域连续的另一种表达方式: m a x − m i n = r − l max-min=r-l maxmin=rl,即 ( m a x − m i n ) − ( r − l ) = 0 (max-min)-(r-l)=0 (maxmin)(rl)=0

考虑 l = 1 , r = n l=1,r=n l=1,r=n 怎么做,我们对 r r r 进行扫描线,维护有多少个 l l l 满足 ( m a x − m i n ) − ( r − l ) = 0 (max-min)-(r-l)=0 (maxmin)(rl)=0

看起来是很难维护的,但实际上可以发现 ( m a x − m i n ) − ( r − l ) ≥ 0 (max-min)-(r-l)\geq 0 (maxmin)(rl)0,而且必然至少一个 l l l 使得 ( m a x − m i n ) − ( r − l ) = 0 (max-min)-(r-l)=0 (maxmin)(rl)=0(如平凡的 l = r l=r l=r 的情况),那么我们考虑用线段树维护 ( m a x − m i n ) − ( r − l ) (max-min)-(r-l) (maxmin)(rl) 的最小值以及最小值的个数(根据刚刚说的,全局最小值的个数就是 ( m a x − m i n ) − ( r − l ) = 0 (max-min)-(r-l)=0 (maxmin)(rl)=0 的个数),然后对于每个 r r r 统计即可。

至于动态维护 ( m a x − m i n ) − ( r − l ) (max-min)-(r-l) (maxmin)(rl),我们可以用单调栈记录不同 m a x max max 值的区间,并动态维护( m i n min min 值同理)。

那么多次询问区间怎么办呢?我们可以先把询问离线下来,然后同样对 r r r 进行扫描线,那么询问时要知道一段区间历史上为最小值的时间有多少,可以用懒标记维护。

具体来说,对于每一个点 u u u 设一个 t a g tag tag 表示 u u u 子树内的所有最小值在历史上有 t a g tag tag 时间也是最小值。

当要修改点 u u u 子树内的某个点时,由于 u u u 子树内的最小值可能变化,所以要向儿子下传标记(需要判断儿子子树内的最小值是否等于 u u u 子树内的最小值)。

代码如下:

#include<bits/stdc++.h>

#define N 120010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct Query
{
	int l,r,id;
}q[N];

bool operator < (Query a,Query b)
{
	return a.r<b.r;
}

int n,m,p[N];
int topmax,smax[N];
int topmin,smin[N];
int minn[N<<2],cnt[N<<2],tagmin[N<<2];
ll ans[N<<2],tagans[N<<2];
ll Ans[N<<2];

void downmin(int k,int v)
{
	minn[k]+=v,tagmin[k]+=v;
}

void downans(int k,int v)
{
	ans[k]+=1ll*cnt[k]*v,tagans[k]+=v;
}

void down(int k)
{
	if(tagmin[k])
	{
		downmin(k<<1,tagmin[k]);
		downmin(k<<1|1,tagmin[k]);
		tagmin[k]=0;
	}
	if(tagans[k])
	{
		if(minn[k]==minn[k<<1]) downans(k<<1,tagans[k]);
		if(minn[k]==minn[k<<1|1]) downans(k<<1|1,tagans[k]);
		tagans[k]=0;
	}
}

void up(int k)
{
	if(minn[k<<1]==minn[k<<1|1]) minn[k]=minn[k<<1],cnt[k]=cnt[k<<1]+cnt[k<<1|1];
	else if(minn[k<<1]<minn[k<<1|1]) minn[k]=minn[k<<1],cnt[k]=cnt[k<<1];
	else minn[k]=minn[k<<1|1],cnt[k]=cnt[k<<1|1];
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		minn[k]=l;
		cnt[k]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	up(k);
}

void update(int k,int l,int r,int ql,int qr,int v)
{
	if(ql<=l&&r<=qr)
	{
		downmin(k,v);
		return;
	}
	down(k);
	int mid=(l+r)>>1;
	if(ql<=mid) update(k<<1,l,mid,ql,qr,v);
	if(qr>mid) update(k<<1|1,mid+1,r,ql,qr,v);
	up(k);
}

ll query(int k,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr) return ans[k];
	down(k);
	int mid=(l+r)>>1;
	ll res=0;
	if(ql<=mid) res+=query(k<<1,l,mid,ql,qr);
	if(qr>mid) res+=query(k<<1|1,mid+1,r,ql,qr);
	return res;
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++) p[i]=read();
	m=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+m+1);
	build(1,1,n);
	int tmp=1;
	for(int i=1;i<=n;i++)
	{
		update(1,1,n,1,n,-1);
		while(topmax&&p[i]>p[smax[topmax]])
		{
			update(1,1,n,smax[topmax-1]+1,smax[topmax],p[i]-p[smax[topmax]]);
			topmax--;
		}
		smax[++topmax]=i;
		while(topmin&&p[i]<p[smin[topmin]])
		{
			update(1,1,n,smin[topmin-1]+1,smin[topmin],p[smin[topmin]]-p[i]);
			topmin--;
		}
		smin[++topmin]=i;
		downans(1,1);
		while(tmp<=m&&q[tmp].r==i)
		{
			Ans[q[tmp].id]=query(1,1,n,q[tmp].l,q[tmp].r);
			tmp++;
		}
	}
	for(int i=1;i<=m;i++)
		printf("%lld\n",Ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值