【題解】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\)
事實上,記錄成功的數對應當用棧,因為如果新來的被某一個搞掉了,就肯定不能影響到後面的,而棧是可以做到這一點的。
於是,我們不斷判斷棧頂的可行性,不斷彈棧直至棧頂滿足條件即可。
最後,別忘了清空棧,並把剩下的這些數對的 \(lst\) 賦值為 1 。
這樣就完成了對 \(lst\) 的處理!
然後,怎麼利用呢?
\(lst_i\) 的定義換句話說就是使 i 成功的最小的 l 。
所以,我們實際要求的就是 \(lst_{l \sim r}\) 中滿足 \(lst_i < l\) 的數的個數。
我們考慮將操作離線,然後把左右端點分別標記到一個數組上,然後從 \(1 \sim n\)
值得注意的是,這裡使用值作為下標,因為我們要查的是值的個數。
中途,如果遇到左端點,那麼先給對應的詢問的答案減去一個 \(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;
}