NOIOL2020 T1丹钓战
前言
来,看我看我,我宣布个事哦!我是个煞*。
解题过程
这道题我想分享一下我做题的过程和心路历程,实在曲折。
首先可以一眼看出的是,我们可以通过求对于任意的 \(i\),这个二元组可以直接或间接弹出其前面的二元组中最小下标,我们定义这个量为 \(lft_i\)。何为间接呢?我们看样例一:
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
对于第 \(4\) 个二元组,它可以直接弹出 \(2,3\),但是没法弹出 \(1\)。我们可以发现 \(2\) 能弹出 \(1\),也就是说,\(4\) 可以间接弹出 \(1\)。
我们该如何求 \(lft_i\) 呢?我们再定义一个 \(brd_i\) 表示第 \(i\) 个二元组可以直接弹出的前面二元组中的最小下标。我们可以发现:\(lft_i=\min\limits_{j=brd_i}^i lft_i\)。这个很容易理解,线段树求解即可。
现在问题转变成了求解 \(brd_i\)。我们可以想到用二分答案来解决。问题就转变到了如何解决 \(\operatorname{check}\) 函数了。
若对于位置 \(j\) 可以被 \(i\) 直接弹出,我们有等式:\(\sum\limits_{k=j}^i [b_i<b_k]=\sum\limits_{k=j}^i [b_i<b_k\ \&\ a_i=a_k]\)。这是显然的。我们 \(\operatorname{check}\) 函数就基于这个式子。左边和右边可以分别用主席树计算。左边很好理解,对于右边,可以对每个 \(a_i\),以其为下标构建一颗主席树,其节点数总和不会很大,还是在 \(O(n\log n)\) 级别的。
最后,我们知道了 \(lft_i\),如何求答案呢?对于询问 \([l,r]\),答案就是 \(\sum\limits_{i=l}^r [lft_i\leq l]\)。这个结构可以用主席树维护,不多赘述。
这个算法时间复杂度为 \(O(n\log^2 n)\)。代码量 200+。
于是在考试时我用 vector 代替了主席树中的数组,喜提 \(0pts\)。调试时发现给出了一个 SIGTRAP
的信号。后来上网查了一下资料发现,vector 和递归会产生一些奇妙的反应,导致程序死掉。
于是赛后我用数组重写了这个程序,把所有的节点放到一个数组里就可以了。我觉得因为这个程序卡不满时间复杂度,所以认为 \(O(n\log^2 n)\) 可以通过,直到我测了大样例……开 O2 跑了 10s。效率有点低。一交发现出题人卡时间卡的很死,\(50pts\) 一分不多给。
于是我开始思考,真的出问题了吗?我们发现时间复杂度的瓶颈在求解 \(brd_i\) 上。我思前想后发现,\(brd_i\) 的求解已经无法加速了。
我们可以越过 \(brd_i\) 直接求 \(lft_i\) 吗?
答案是,可以的。我们发现 \(lft_i\) 其实就是我们从 \(1\) 开始模拟这个入栈出栈的过程在 \(i\) 及间入栈时栈顶元素编号。这样我们可以 \(O(n)\) 求出 \(lft_i\)。
进一步的,我们其实可以用树状数组来维护答案式,也就是说,代码量骤减至 \(60\) 行……
所以理解我在前言中的话了吧……
代码
纪念一下自己写的主席树,就不想改了。
//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
char read_char() {
char ch=getchar();
while(!isalpha(ch)) {
ch=getchar();
}
return ch;
}
const int MAXN=5e5+10;
int n,q,tot,cnt;
int a[MAXN],b[MAXN],num[MAXN],m[MAXN],brd[MAXN],id[MAXN],pre[MAXN],rt[MAXN<<2];
vector<int> idx[MAXN];
struct seg {
int sum,ls,rs;
}s[MAXN*73],t[MAXN<<2];
int newnode() {
tot++;
return tot;
}
void build(int l,int r,int &p) {
if(!p) {
p=newnode();
}
if(l==r) {
return ;
}
int mid=(l+r)>>1;
build(l,mid,s[p].ls);
build(mid+1,r,s[p].rs);
}
int modify(int l,int r,int pp,int &p,int x) {
if(p>tot) {
int ttt;
ttt++;
}
if(!p) {
p=newnode();
}
if(l==r) {
s[p].sum=s[pp].sum+1;
return p;
}
int mid=(l+r)>>1;
if(x<=mid) {
s[p].rs=s[pp].rs;
s[p].ls=modify(l,mid,s[pp].ls,s[p].ls,x);
}
else {
s[p].ls=s[pp].ls;
s[p].rs=modify(mid+1,r,s[pp].rs,s[p].rs,x);
}
s[p].sum=s[s[p].ls].sum+s[s[p].rs].sum;
return p;
}
int query(int l,int r,int p1,int p2,int x,int y) {
if(y<l||r<x) {
return 0;
}
if(x<=l&&r<=y) {
return s[p2].sum-s[p1].sum;
}
int mid=(l+r)>>1;
return query(l,mid,s[p1].ls,s[p2].ls,x,y)+query(mid+1,r,s[p1].rs,s[p2].rs,x,y);
}
stack<int> stk;
int main() {
cin>>n>>q;
for(int i=1;i<=n;i++) {
a[i]=read();
idx[a[i]].push_back(i);
}
for(int i=1;i<=n;i++) {
b[i]=read();
}
for(int i=1;i<=n;i++) {
while(!stk.empty()&&!(a[i]!=a[stk.top()]&&b[i]<b[stk.top()])) {
stk.pop();
}
if(stk.empty()) {
brd[i]=1;
}
else {
brd[i]=stk.top()+1;
}
stk.push(i);
}
for(int i=1;i<=n;i++) {
modify(1,n,rt[2*n+i-1],rt[2*n+i],brd[i]);
}
while(q--) {
int l,r;
l=read();r=read();
int ans=query(1,n,rt[2*n+l-1],rt[2*n+r],1,l);
printf("%d\n",ans);
}
return 0;
}