题解:NOI Online 2022 提高组 T1 丹钓战

Solution for NOI Online 2022 提高组 T1 丹钓战

算法:单调栈,可持久化线段树(主席树)

1.题意

简要概括:

给出 \(n\) 个二元组 \(a_i,b_i\) ,并给定比较关系:对于 \(i,j\) 两个二元组,若 \(a_i=a_j\)\(b_i\geq b_j\) ,则在单调栈中 \(j\) 作为栈顶 \(i\) 作为新二元组时 \(j\) 被弹出。

给出 \(q\) 组询问,每次询问一个区间 \(l\)\(r\) 之间的二元组,若做一遍单调栈操作,会有多少个二元组成为栈顶。

\(n,q\leq5*10^5,1\leq a_i,b_i\leq n,1\leq l_i\leq r_i\leq n\)

2.部分分解法

对于 \(n\leq 1000\) 的数据,直接暴力模拟即可。

扩展到 \(n\leq 5000\) 的数据,需要提前做预处理,可以采用固定左端点移动右端点的方法,做到 \(O(1)\) 查询。

对于 \(n\leq 10^5\) 的数据,博主不知道怎么做,听说有根号或两个 \(log\) 的做法,但似乎对正解的启发不大,不赘述了。

\(b_i=n-i+1\) 的数据,我们逐渐开始对正解的思考了。

可以发现这档数据把比较函数中 \(b\) 的限制默认永远成立,所以只需要看 \(a\) 是否相同。

很容易发现,\(l\)\(r\) 区间的二元组成为栈顶,当且仅当这些元素是从 \(l\) 开始 \(a\) 相同的元素。

所以考虑从后往前dp,预处理出每个位置对应的最长的 \(a\) 连续相同的段 \(len[i]\)

那么答案就是 \(min(r-l+1,len[l])\)

到这里,我们隐约发现,可以预处理出来一些东西辅助询问。

对于 \(a_i=i\) 的数据,博主没想到怎么做,所以下面直接开始正解。

3.正解

首先,我们可以预处理一个 \(pre[]\) 数组,\(pre[i]\) 的含义是,利用题目的比较函数,他比前面最多连续多少个元素都“大”。

换句话说,如果左端点是在 \(pre[i]\)\(i\) 之间的,那么 \(i\) 这个元素必然可以成为栈顶,反之不行。

类比普通的单调栈, \(pre[]\) 数组是很容易处理的,不再赘述。

接下来考虑求答案。

如果暴力去做的话,只需要统计 \(l\)\(r\) 之间有多少 \(pre[i]\leq l\) 的元素就好了。

考虑加速这一过程,不难想到用可持久化线段树。

考虑用值域线段树,维护区间内每个值域有多少 \(pre\) ,用 \(siz\) 表示。

对于每个位置,建出来一棵值域线段树,并在 \(pre[i]\) 的位置把 \(siz++\)

之后把当前的线段树和前面的线段树合并到一起,并可持久化,方便查询。

查询的时候,在 \(l\)\(r\) 的值域线段树上,查找 \(1\)\(l\)\(siz\) 大小就是答案了。

考虑简化查询操作。毕竟 \(1\)\(l-1\) 之间所有的 \(pre[i]\) 必然是小于 \(l\) 的,所以不需要用可持久化线段树的查询,只需要得到 \(r\) 对应的线段树像普通线段树的查询结果,减去 \(l-1\) 就是答案了。

具体实现详见代码。

4.code

因为可持久化数据结构常数较大,建议尽量卡常数。

#include<bits/stdc++.h>
using namespace std;
struct IO{
}fin,fout;//略去IO优化
const int N=5e5+5;
int a[N],b[N];
int n,m;
namespace task{
	inline bool cmp(int x,int y){
		if(a[x]!=a[y]&&b[x]<b[y]) return false;
		return true;
	}
	int stk[N],top=0;
	int pre[N];
	void init(){
		for(int i=1;i<=n;i++){
			while(top&&cmp(i,stk[top])) top--;
			pre[i]=stk[top]+1;
			stk[++top]=i;
		}
	}
	int root[N];
	int idx=0;
	struct segtree{
		int ls,rs,siz;
		#define ls(p) tr[p].ls
		#define rs(p) tr[p].rs
		#define siz(p) tr[p].siz
	}tr[N*60];
	void merge(int &q,int p,int l,int r,int x){
		q=++idx;ls(q)=ls(p);rs(q)=rs(p);siz(q)=siz(p);
		if(l==r){
			siz(q)++;
			return ;
		}
		int mid=(l+r)>>1;
		if(x<=mid) merge(ls(q),ls(p),l,mid,x);
		else merge(rs(q),rs(p),mid+1,r,x);
		siz(q)=siz(ls(q))+siz(rs(q));
	}
	int get(int p,int l,int r,int L,int R){
		if(L<=l&&r<=R) return siz(p);
		int mid=(l+r)>>1,ret=0;
		if(L<=mid) ret+=get(ls(p),l,mid,L,R);
		if(R>mid) ret+=get(rs(p),mid+1,r,L,R);
		return ret;
	}
	void solve(){
		init();
		for(int i=1;i<=n;i++) merge(root[i],root[i-1],1,n,pre[i]);
		while(m--){
			int l,r;fin>>l;fin>>r;
			int ans=get(root[r],1,n,1,l)-(l-1);
			fout<<ans;fout.pc('\n');
		}
	}
}
int main(){
	fin>>n;fin>>m;
	for(int i=1;i<=n;i++) fin>>a[i];
	for(int i=1;i<=n;i++) fin>>b[i];
	task::solve();
	return 0;
}
posted @ 2022-03-28 22:02  Displace  阅读(80)  评论(1)    收藏  举报