[NOI Online 2022 提高组] 丹钓战

题目背景

经过管理员的考虑,我们打算将民间数据单独存放在最后一个 Subtask 中。这些测试点分数均为 0 分,但是没有通过其中的任何测试点将会视为此题不通过。

这一题中出现了评测记录测试点编号错乱的问题,是民间数据命名方式冲突导致的。但是我们保证其相对顺序没有混乱。

民间数据提供者:@Froggy。

题目描述

有 nn 个二元组 (a_i, b_i)(ai​,bi​),编号为 11 到 nn。

有一个初始为空的栈 SS,向其中加入元素 (a_i, b_i)(ai​,bi​) 时,先不断弹出栈顶元素直至栈空或栈顶元素 (a_j , b_j)(aj​,bj​) 满足 a_i \neq a_jai​=aj​ 且 b_i < b_jbi​<bj​,然后再将其加入栈中。

如果一个二元组入栈后栈内只有这一个元素,则称该二元组是“成功的”。

有 qq 个询问 [l_i, r_i][li​,ri​],表示若将编号在 [l_i, r_i][li​,ri​] 中的二元组按编号从小到大依次入栈,会有多少个二元组是“成功的”。

询问之间相互独立。

输入格式

第一行两个正整数 n,qn,q。

第二行 nn 个正整数表示 a_iai​。

第三行 nn 个正整数表示 b_ibi​。

接下来 qq 行,每行两个正整数 l_i, r_ili​,ri​,表示一组询问。

输出格式

qq 行,每行一个自然数表示一组询问的答案。

输入输出样例

输入 #1复制

10 4
3 1 3 1 2 3 3 2 1 1
10 10 2 9 7 5 4 7 6 1
1 4
7 8
7 10
1 8

输出 #1复制

3
2
2
3

说明/提示

【样例解释】

以第一次询问 [1, 4][1,4] 为例。

一开始栈为 \{\}{}。

加入 11 号二元组后栈为 \{(3, 10)\}{(3,10)},栈中只有一个元素,该二元组是“成功的”。

加入 22 号二元组 (1, 10)(1,10) 时,栈顶的 (3, 10)(3,10) 的 bb 值不大于 22 号二元组的,因此弹栈。此时栈空,22 号二元组入栈,栈为 \{(1, 10)\}{(1,10)},该二元组是“成功的”。

加入 33 号二元组 (3, 2)(3,2),此时栈顶元素与之 aa 值不同,bb 值比它更大,因而不需要弹栈,直接将 33 号二元组入栈,栈为 \{(1, 10),(3, 2)\}{(1,10),(3,2)},栈中有多个元素,该二元组不是“成功的”。

加入 44 号二元组 (1, 9)(1,9),此时栈顶元素 (3, 2)(3,2) 的 bb 值比它小,弹栈。弹栈后栈顶元素 (1, 10)(1,10) 与 (1, 9)(1,9) 的 aa 值相同,继续弹栈。此时栈空,44 号二元组入栈,栈为 \{(1, 9)\}{(1,9)},该二元组是“成功的”。共有 33 个二元组是“成功的”,因而答案为 33。

【样例 2,3,4】

见附件 \texttt{stack/stack*.in}stack/stack*.in 与 \texttt{stack/stack*.ans}stack/stack*.ans。

链接:百度网盘 请输入提取码 提取码:gugu

【数据范围与提示】

对于所有测试点:1 \leq n, q \leq 5 \times 10^51≤n,q≤5×105,1 \leq a_i, b_i \leq n1≤ai​,bi​≤n,1 \leq l_i \leq r_i \leq n1≤li​≤ri​≤n。

每个测试点的具体限制见下表:

测试点编号特殊性质
1 \sim 31∼3n,q \leq 1000n,q≤1000
4 \sim 64∼6n \leq 5000n≤5000
7 \sim 107∼10n,q \leq 10^5n,q≤105
11 \sim 1211∼12b_i=n-i+1bi​=n−i+1
13 \sim 1513∼15a_i=iai​=i
16 \sim 2016∼20

 

虽然题面上已经提醒我们要用单调栈了。

但是我们要有叛逆创新思维。

首先观察到 1 \leq n, q \leq 5 \times 10^51≤n,q≤5×105,这个数据范围说明出题人不仅提示我们可以用单调栈,还可以用莫队做

知道出题人给我们的提示后,我们就可以开始莫队了。

首先我们预处理一个 fa 数组,然后这个 fa 数组是干什么用的呢,是用来记录假如从第1个开始放,那么当第 ii 个二元组入栈前栈顶的下标。

实现也肥肠简单:

	k=i-1;
	while(k&&((a[k]==a[i])||((a[k]^a[i])&&b[k]<=b[i])))
		k=fa[k];
	fa[i]=k;

实际上原理就是模拟一下栈的情况,随便手推一下就可以了。

另外对于每个下标 ii 我们都开一个 vector cnt 记录所有将 ii 作为 fa 的位置:

    cnt[i].emplace_back(-0x7ffffff);//先压一个方便二分
    cnt[k].emplace_back(i);

易知预处理是 O(n)O(n) 的。

接下来就是莫队环节了。

首先我们考虑右指针的移动,比较简单:

右端的移动表示把一个新的数压进栈内。

右移(压栈):

	while(q[i].r>r)
		ans+=(fa[++r]<l?1:0);

假如阻挡这个二元组到达栈底的二元组不在当前的询问区间(没有入栈)那么自然这个数可以一通到底;

左移(弹栈):

	while(q[i].r<r)
		ans-=(fa[r--]<l?1:0);

同理假如这个二元组做出了贡献就要减掉。

然后来考虑左指针。

左指针移动有一点麻烦,因为不管是在操作序列最前端加入或者删除都有可能影响操作序列后端的二元组对答案的贡献。

这个时候之前预处理的 cnt 就有用了:

左移(加入):

	while(q[i].l<l)
	{
		l--,ans++;
		vector<int>::iterator t1=upper_bound(cnt[l].begin(),cnt[l].end(),l);
		vector<int>::iterator t2=upper_bound(cnt[l].begin(),cnt[l].end(),r)-1;
		if(t1!=cnt[l].end()&&t2!=cnt[l].begin())
		ans-=(t2-t1+1);
	}

二分找到在区间 [l,r][l,r] 内以 ii 为 fa 的二元组。

由于假如 ii 没有加入,那么这些二元组可以为答案做出贡献,现在加入了,就要减掉。

另外由于 ii 成为了第一个入栈的,所以 ii 会对答案产生贡献,所以 ans++ 。

同理可得右移(删除):

	while(q[i].l>l)
	{
		vector<int>::iterator t1=upper_bound(cnt[l].begin(),cnt[l].end(),l);
		vector<int>::iterator t2=upper_bound(cnt[l].begin(),cnt[l].end(),r)-1;
		if(t1!=cnt[l].end()&&t2!=cnt[l].begin())
			ans+=(t2-t1+1);
		l++,ans--;
	}

时间复杂度 n\sqrt {nlogn}nnlogn​ ,应该还有更紧的上界,但是我懒得算了(

我也不知道为什么这东西为什么可以过,总之各种玄学卡常就过了

code:

#include<bits/stdc++.h>
#define Maxn 500100
using namespace std;
namespace DEBUG {
	inline void cerr_out(){cerr<<'\n';}
	template<typename Head,typename... Tail>
	inline void cerr_out(Head H,Tail... T){cerr<<' '<<H,cerr_out(T...);}
	void debug_out() { cerr << '\n'; }
	template <typename Head, typename... Tail>
	void debug_out(Head H, Tail... T) { cerr << ' ' << H, debug_out(T...); }
#define debug(...) cerr << '[' << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
} using namespace DEBUG;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++)
namespace get_out
{
	char B[1<<20],*S=B,*T=B;
	char op;
	inline void read_(int &x)
	{
		x=0;
		int fi(1);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1;
		while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
		x*=fi;
		return;
	}
	inline void read_(long long &x)
	{
		x=0;
		int fi(1);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1;
		while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
		x*=fi;
		return;
	}
	inline void read_(double &x)
	{
		x=0.0;
		float fi(1.0),sm(0.0);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1.0;
		while(op>='0'&&op<='9') x=(x*10.0)+(op^48),op=getchar();
		if(op=='.') op=getchar();
		int rtim=0;
		while(op>='0'&&op<='9') sm=(sm*10.0)+(op^48),++rtim,op=getchar();
		while(rtim--) sm/=10.0;
		x+=sm,x*=fi;
		return;
	}
	inline void read_(string &s)
	{
		char c(getchar());
		s="";
		while(!isgraph(c)&&c!=EOF) c=getchar();
		for(;isgraph(c);c=getchar()) s+=c;
	}
	inline void read_(char &c)
	{
		for(c=op;c==' '||c=='\n'||c=='\r'||c=='\t';c=getchar());
		op=c;
	}
	
	template<typename Head,typename ...Tail>
	inline void read_(Head& h,Tail&... t)
	{read_(h),read_(t...);}
	
	inline void write_(){}
	inline void postive_write(unsigned x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void postive_write(unsigned long long x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void postive_write(int x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void postive_write(long long x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void postive_write(short x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void write_(const char* ss) {while(*ss) putchar(*ss++);}
	inline void write_(char c) {putchar(c);}
	inline void write_(string s) {for(unsigned i=0;i<s.size();++i) putchar(s[i]);}
	inline void write_(short x)
	{
		if(x<0) putchar('-'),postive_write(-x);
		else postive_write(x);
	}
	inline void write_(int x)
	{
		if(x<0) x=-x,putchar('-');
		postive_write(x);
	}
	inline void write_(long long x)
	{
		if(x<0) x=-x,putchar('-');
		postive_write(x);
	}
	inline void write_(unsigned x) {postive_write(x);}
	inline void write_(ull x) {postive_write(x);}
	
	template<typename Head,typename ...Tail>
	inline void write_(Head h,Tail ...t) {write_(h),write_(t...);}
}
using get_out::read_;
using get_out::write_;
//dalao嘉年华提供的快到飞起的快读
vector<int>cnt[Maxn<<1];
int n,F,m,fa[Maxn<<1];
int a[Maxn<<1],b[Maxn<<1],ans_[Maxn<<1];
struct ques
{
	int l,r,id;
	int bl;
}q[Maxn<<1];
inline bool cmp(ques &a,ques &b)
{
	return a.bl!=b.bl?a.bl<b.bl:a.r<b.r;
}
int main()
{
	read_(n,m);
	F=pow(n,0.45)+5;
	for(register int i=1;i<=n;i++) read_(a[i]);
	for(register int i=1;i<=n;i++) read_(b[i]);
	for(register int i=1,k;i<=n;i++)
	{
		cnt[i].emplace_back(-0x7ffffff);
		k=i-1;
		while(k&&((a[k]==a[i])||((a[k]^a[i])&&b[k]<=b[i])))
			k=fa[k];
		cnt[fa[i]=k].emplace_back(i);
	}
	for(register int i=1;i<=m;i++)
	{
		read_(q[i].l,q[i].r);
		q[i].id=i,q[i].bl=q[i].l/F;
	}
	sort(q+1,q+m+1,cmp);
//	cerr<<clock();
	int l=1,r=1,ans=1;
	for(register int i=1;i<=m;i++)
	{
		while(q[i].r>r)
			ans+=(fa[++r]<l?1:0);
		while(q[i].l<l)
		{
			l--,ans++;
			vector<int>::iterator t1=upper_bound(cnt[l].begin(),cnt[l].end(),l);
			vector<int>::iterator t2=upper_bound(cnt[l].begin(),cnt[l].end(),r)-1;
			if(t1!=cnt[l].end()&&t2!=cnt[l].begin())
				ans-=(t2-t1+1);
		}
		while(q[i].r<r)
			ans-=(fa[r--]<l?1:0);
		while(q[i].l>l)
		{
			vector<int>::iterator t1=upper_bound(cnt[l].begin(),cnt[l].end(),l);
			vector<int>::iterator t2=upper_bound(cnt[l].begin(),cnt[l].end(),r)-1;
			if(t1!=cnt[l].end()&&t2!=cnt[l].begin())
				ans+=(t2-t1+1);
			l++,ans--;
		}
		ans_[q[i].id]=ans;
	}
	for(register int i=1;i<=m;i++) write_(ans_[i],'\n');
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值