NOI Online 2022t1 丹钓站 题解

《论为什么现场的我想不出 O ( n log ⁡ n ) O(n\log n) O(nlogn) 做法只能用莫队拿 65 分这件事》

传送门


Step 1

关于丹钓站单调栈,我们有一个很好的性质。我们先按照题目的规定将 n n n 个二元组模拟一遍入栈过程。在过程中对每一个元素 ( a i , b i ) (a_i,b_i) (ai,bi),记录 p r e i pre_i prei 表示这个元素进栈时栈顶元素的下标,若栈空则记为 -1。

你试几个数据后就会发现,得到所有 p r e i pre_i prei 之后,问题瞬间变简单了。假设答案询问的区间是 [ l , r ] [l,r] [l,r],那么对于区间中的每个二元组 ( a i , b i ) (a_i,b_i) (ai,bi),如果 p r e i < l pre_i<l prei<l,则二元组 ( a i , b i ) (a_i,b_i) (ai,bi) 是“成功的”,反之则不是“成功的”。这个性质其实是比较显然的。

Step 2

现在问题被我们转化为:

n n n p r e i pre_i prei,每次询问区间 [ l , r ] [l,r] [l,r] p r e i < l pre_i<l prei<l 的个数。

我们希望去用线段树BIT 处理这样的区间问题。

然后,我们发现这个 l l l 很讨厌,它不是固定的。这样我们自然而然地想到把它变得有序,就会很好处理。那就先离线下来,将所有询问按 l l l 升序排序。同时也将所有下标 i i i p r e i pre_i prei 升序排序

接下来,我们维护线段树或 BIT,这里用 c i c_i ci 表示元素 i i i 当前是否满足 p r e i < l pre_i<l prei<l,是则为 1,否则为 0。

每次对于一个询问 [ l , r ] [l,r] [l,r],我们将所有 p r e i < l pre_i<l prei<l c i = 0 c_i=0 ci=0 的元素令 c i ← 1 c_i\gets 1 ci1,然后查询 ∑ i = l r c i \sum\limits_{i=l}^rc_i i=lrci 作为询问答案。

最后按照询问编号输出。


Code

#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define F first
#define S second
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
inline ll read()//快读快写
{
	ll x=0,f=0;
	char ch=getchar();
	while(!isdigit(ch))
		f|=ch=='-',ch=getchar();
	while(isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
template<typename T>inline void write(T x)
{
     if(x<0)x=-x,putchar('-');
     if(x>9)write(x/10);
     putchar(x%10^48);
}
int n,m;//n:元素个数,m:询问个数
pii a[500005],pre[500005];
int st[500005],tp,ans[500005],sum[1000005];
struct Query//定义询问结构体
{
	int l,r,id;
	bool operator<(Query &B)
	{
		return l!=B.l? l<B.l : r<B.r;
	}
}q[500005];
void modify(int l,int x)//线段树单点修改
{
	l+=n;
	sum[l]+=x;
	while(l>1)
	{
		l>>=1;
		sum[l]=sum[l<<1]+sum[l<<1|1];
	}
}
int query(int l,int r)//线段树区间查询
{
	int res=0;
	for(l+=n,r+=n;l<r;l>>=1,r>>=1)
	{
		if (l&1) res+=sum[l++];
		if (r&1) res+=sum[--r];
	}
	return res;
}
int main()
{
	//freopen("stack.in","r",stdin);
	//freopen("stack.out","w",stdout);
	n=read(),m=read();
	for(int i=0;i<n;i++)
		a[i].F=read();
	for(int i=0;i<n;i++)
		a[i].S=read();
	for(int i=0;i<m;i++)
		q[i].l=read()-1,q[i].r=read()-1,q[i].id=i;
	sort(q,q+m);//对询问按左端点排序
	for(int i=0;i<n;i++)//模拟入栈过程
	{
		while(tp&&!(a[st[tp]].F!=a[i].F&&a[st[tp]].S>a[i].S))
			--tp;
		pre[i].F=(tp==0?-1:st[tp]);
		pre[i].S=i;
		st[++tp]=i;
	}
	sort(pre,pre+n);//对下标按pre排序
	int cur=0;//维护当前第一个pre>=l的下标
	for(int i=0;i<m;i++)
	{
		while(cur<n&&pre[cur].F<q[i].l)
		{
			modify(pre[cur].S,1);
			cur++;
		}
		ans[q[i].id]=query(q[i].l,q[i].r+1);//我的线段树区间[l,r)左闭右开
	}
	for(int i=0;i<m;i++)
		write(ans[i]),putchar('\n');
	return 0;
}

时间复杂度: O ( n log ⁡ n + m log ⁡ m ) O(n\log n + m\log m) O(nlogn+mlogm)

空间复杂度: O ( n + m ) O(n+m) O(n+m)


Summary

我个人认为这是一道很不错的题目 ,就是谐音梗俗了点 。它很好的把单调栈的性质、区间操作和离线排序的技巧结合在一起。对我这种蒟蒻还是有些思维难度,希望我下次不会被这样的题难住了罢。

希望题解对你有帮助!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值