【题解】P8251 [NOI Online 2022 提高组] 丹钓战(官方数据)

题目传送门

题面

给你 n 个数对 (a,b) 和一个栈 S 。

向 S 中添加数对 \((a_i,b_i)\) 时,先不断弹出栈顶直至栈空或者栈顶的 \((a_j,b_j)\) 满足 \(a_i \neq a_j ,b_i<b_j\)

现给你 q 组 \(l,r\) ,问你加入 \(l\sim r\) 的数对时,有多少数对是成功的。

一个数对是成功的,当且仅当加入这个数对后栈中只有一个元素。

思路

先来考虑,如果一个点是成功的,那么什么会让它变得不成功呢?

首先,在它后面加入的元素时不会恶心到它的,但是前面的会。然而,多一个数就多一个条件,所以元素增加可能让它挂掉,然鹅减少并不会。

总的来说,l 越小,一个成功的数对就越容易挂掉。

那么,我们考虑维护一个 \(lst_i\) ,表示第 i 个数对在 l 缩小至 \(lst_i\) -1 时会挂掉。

它怎么求呢?

因为右端点无所谓,我们索性把它固定在 n 的位置,不管它了,然后从 n 到 1 不断缩小 l ,更新 \(lst_i\)

我们在缩小 l 的时候,可以记录下 l 在当前位置时成功的数对,对于 l-1 对应的数对,我们来对它们进行更新。

假设它们的在 k 的位置,那么显然, \(l\sim k-1\) 的所有数对它都能弹掉,所以我们只需要考虑它能不能弹掉新来的,如果弹不掉就趋势。

很显然,如果它连新来的都弹不掉,也就不用想前面的了,所以 \(lst_k\) 也就一定是 l+1 了( l 时趋势意味着 l+1 是最后一个可行的位置)。

事实上,记录成功的数对应当用栈,因为如果新来的被某一个搞掉了,就肯定不能影响到后面的,而栈是可以做到这一点的。

于是,我们不断判断栈顶的可行性,不断弹栈直至栈顶满足条件即可。

最后,别忘了清空栈,并把剩下的这些数对的 \(lst\) 赋值为 1 。

这样就完成了对 \(lst\) 的处理!

然后,怎么利用呢?

\(lst_i\) 的定义换句话说就是使 i 成功的最小的 l 。

所以,我们实际要求的就是 \(lst_{l \sim r}\) 中满足 \(lst_i < l\) 的数的个数。

我们考虑将操作离线,然后把左右端点分别标记到一个数组上,然后从 \(1 \sim n\) 遍历一遍 \(lst\) 数组,然后不断插入树状数组中。

值得注意的是,这里使用值作为下标,因为我们要查的是值的个数。

中途,如果遇到左端点,那么先给对应的询问的答案减去一个 \(sum(now)\) (sum表示查询值域为 1~now 的数的个数),而且需要注意,这次查询应该在插入之前,不然可能会把 now 减掉。而为什么 sum 里面是 now 呢,这个也是显然的,因为遇到的是左端点,所以查询的值域理应在 \(1 \sim now\)

而如果遇到右端点呢?

我们发现,需要记录它对应的左端点,这需要我们提前处理,这里设它是 \(x\)

而后,我们将对应的询问的答案加上一个 \(sum(x)\) ,而它肯定不会多,因为 \(1\sim x-1\) 所对应的 \(sum(x)\) 已经提前被做掉了。

之后,便可以快乐输出啦!

十年OI一场空,不删调试语句见祖宗QwQ

代码

//吾日八省吾身:
//输入多而不快读乎?
//题目标注而不freopen乎?
//乘除并列先乘后除乎?
//不手撕样例直接写代码乎?
//不仔细读题直接关页面乎?
//1e9而不开long long乎?
//Ctrl+V而不改名称乎?(papaw->papan IMPLIES tg1=->2=)
//相信评测神机乎?
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime>
#include<sstream>
#include<list>
#include<bitset>
using namespace std;
typedef long long ll;
const ll MAXN(500233);
ll tree[MAXN],n,q,lst[MAXN];
ll stk[MAXN],cnt;
ll ans[MAXN];
struct twt{
	ll a,b;
}tt[MAXN]; 
vector<ll> ls[MAXN];
vector<ll> rs[MAXN];
ll getl[MAXN];//只和l有关,我们需要知道l是多少 
ll lowbit(ll x){return x&(-x);}
void add(ll x,ll num){
	while(x<=n){
		tree[x]+=num;
		x+=lowbit(x);
	} 
	return;
}
ll sum(ll x){
	ll suu=0;
	while(x>=1){
		suu+=tree[x];
		x-=lowbit(x);
	}
	return suu;
}
void R(ll &x){
	x=0;ll f=1;char c='c';
	while(c>'9'||c<'0'){
		f=f*(c=='-'?-1:1);
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=x*10+c-'0';
		c=getchar();
	}
	return;
}
int main(){
	R(n);R(q);
	for(int i=1;i<=n;++i) R(tt[i].a);
	for(int i=1;i<=n;++i) R(tt[i].b);
	for(int i=n;i>=1;--i){
		while(((tt[i].a!=tt[stk[cnt]].a)&&(tt[i].b>tt[stk[cnt]].b))&&cnt){
			lst[stk[cnt]]=i+1;//i的时候挂了说明i+1是最后还行的了
			cnt--; 
		}
		stk[++cnt]=i;
	}
	while(cnt){lst[stk[cnt]]=1;cnt--;}//如果这样还能成功,就意味着l可以取1
	for(int i=1;i<=q;++i){
		ll l,r;
		R(l);R(r);
		ls[l].push_back(i);
		rs[r].push_back(i);
		getl[i]=l;//i号询问的左端点 
	} 
	for(int i=1;i<=n;++i){
		for(auto j=ls[i].begin();j!=ls[i].end();++j) ans[*j]-=sum(i);//现在的sum_i是1~i-1范围内≤i的数的个数 
		add(lst[i],1);
		for(auto j=rs[i].begin();j!=rs[i].end();++j) ans[*j]+=sum(getl[*j]);//查询1~i范围内≤i对应的左端点的下标的数的个数 
	}
	for(int i=1;i<=q;++i) printf("%lld\n",ans[i]);
	return 0;
}
posted @ 2022-03-31 14:12  Binaries  阅读(136)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end