题解: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;
}

浙公网安备 33010602011771号