1. 程式人生 > 其它 >【題解】P8251 [NOI Online 2022 提高組] 丹釣戰(官方資料)

【題解】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;
}