ABC372D ABC379F 題解 單調棧二分
一直覺得AT上面學到的東西比CF要多一些,無意捧一踩一,但可能是我太菜的原因,畢竟ABC的題目普遍要比Div.2 簡單一些。
好多次碰到這個單調棧裡面二分的 trick 了,所以寫一篇來總結一下。
ABC 372 D
形象地給定一系列 Buildings 的高度 \(h\) ,保證每個 \(h\) 不相等。
問一共有多少對 \((i,j)\) 滿足 \(i< j\) 並且其間不存在比 \(j\) 高的建築。
我們考慮對於一個 \(j\) ,如果它之前存在一個比它高的建築 \(j'\) ,那麼對於所有 \(j'\) 之前的 \(i\) ,其都不能和 \(j\) 構成一個合法的對,那麼這個 \(j\) 對於所有這樣的 \(i\) 都是可以不用考慮的。
維護
那麼這樣的話我們其實就可以倒序列舉 \(i\) ,然後維護一個從頂到底單調遞增的單調棧,被彈出的元素一定對現在以及之後所有的 \(i\) 不會有貢獻了,所以可以直接彈出。
統計答案
每個答案實際上就是當前棧裡的元素個數。
後話
貌似這個版本是不用再單調棧上面二分的,然而我當時如同一個 2b ,不僅使用了二分,甚至還用了差分和字首和來統計每一個固定的 \(j\) 對 \(i\) 的貢獻,而不是直接計算 \(i\) 的答案。
不過這也倒是間接為我在這個強化版問題上面提供了思路,導致想起來沒有什麼困難。
ABC 379 F
這個就是給定若干個 \((l_i,r_i)\) ,問在 \(r_i\) 的右邊有多少建築能夠被 \(l_i\) 和 \(r_i\) 處的建築同時看到。
分析
不難發現,滿足答案的建築所必須滿足的必要條件是能夠被 \(l_i\) 看到,當其序號滿足在 \(r_i\) 右邊的時候,這就變成了一個充要條件了。
所以我們直接離線,然後按左端點從小到大排序,然後倒序列舉區間,把所有 \(l_i\) 之後序號的放進單調棧裡面,之後再二分找所有序號大於 \(r_i\) 的即可
維護
不難發現,我們即使不彈出,單調棧裡面元素對應的序號也一定是單調的,所以單調棧裡可以直接存序號,我們在維護的時候透過序號訪問高度,在查詢的時候直接查詢序號即可。
Code
#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void re(T &x)
{
x=0;int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
template<typename T>inline void wr(T x)
{
if(x>9)wr(x/10);
putchar(x%10^48);
}
inline void out(int x){wr(x),putchar('\n');}
const int N=2e5+10;
int stk[N],top;
struct seg
{
int l,r,idx;
}a[N];
inline bool cmp(seg x,seg y)
{
return x.l<y.l;
}
int ans[N];
int main()
{
int n,q;
cin>>n>>q;
vector<int> h(n+1);
for(int i=1;i<=n;++i)re(h[i]);
for(int i=1;i<=q;++i)
re(a[i].l),re(a[i].r),a[i].idx=i;
sort(a+1,a+q+1,cmp);
auto insert=[&](int x)
{
while(top&&h[x]>h[stk[top]])top--;
stk[++top]=x;
};
auto bs=[&](int id)
{
if(id>=stk[1])return 0;
int l=1,r=top;
while(l<r)
{
int mid=(l+r+1)>>1;
if(stk[mid]>id)l=mid;
else r=mid-1;
}
return l;
};
int las=n;
for(int i=q;i>=1;--i)
{
for(int id=las;id>a[i].l;--id)
insert(id);
las=a[i].l;
ans[a[i].idx]=bs(a[i].r);
// printf("%d:%d\n",a[i].idx,bs(a[i].r));
}
for(int i=1;i<=q;++i)out(ans[i]);
return 0;
}