【CF526F】Pudding Monsters(分治套路題)
阿新 • • 發佈:2020-11-19
- 一個\(n\times n\)的正方形,其中每行每列恰有一個點。
- 問有多少子正方形,也滿足每行每列恰有一個點。
- \(n\le3\times10^5\)
二維化一維
顯然這道題很容易想到降維。
既然每行只有一個點,我們可以用\(a_i\)表示第\(i\)行的點在第幾列。
現在問題就變成有多少區間滿足其中元素排序之後值連續。
則區間\([l,r]\)合法的充要條件就是\(\max_{i=l}^ra_i-\min_{i=l}^ra_i=r-l\)。
分治套路題
對於當前分治區間\([l,r]\),設其中點為\(mid\),那麼\([l,mid]\)和\([mid+1,r]\)兩區間各自的答案可以直接遞迴處理。
接著我們先預處理出\(Mx,Mn\)兩個陣列,當\(i\in[l,mid]\)時表示\([i,mid]\)的最值,當\(i\in[mid+1,r]\)時表示\([mid+1,i]\)的最值。
現在我們就要考慮如何統計跨中點的區間的答案,分成兩類情況。
兩最值在中點同側
假設在\([l,mid]\)中。
我們列舉左端點\(i\),此時最值為\(Mx_i,Mn_i\),所以滿足\(Mx_i-Mn_i=j-i\),即\(j=Mx_i-Mn_i+i\)。
那麼我們只要驗證一下\(j\)是不是一個合法的右端點即可。
兩最值在中點異側
假設最小值在\([l,mid]\)中,最大值在\([mid+1,r]\)
我們從大到小列舉\(i\),同時開\(p,q\)兩個指標維護出區間\([p,q]\)表示合法右端點\(j\)的取值區間,顯然這東西隨著\(i\)的移動是單調右移的。
此時最值為\(Mx_j,Mn_i\),故\(Mx_j-Mn_i=j-i\),即\(Mn_i-i=Mx_j-j\)。
那麼只要開桶記一下\(Mx_j-j=x\)的\(j\)的個數,就可以了。
程式碼:\(O(nlogn)\)
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 300000 #define LL long long using namespace std; int n,a[N+5];LL ans; int Mx[N+5],Mn[N+5],c[2*N+5];I void Solve(CI l,CI r)//分治 { if(l==r) return (void)++ans;RI i,p,q,mid=l+r>>1;Solve(l,mid),Solve(mid+1,r);//遞迴子區間 Mx[mid]=Mn[mid]=a[mid],Mx[mid+1]=Mn[mid+1]=a[mid+1]; for(i=mid-1;i>=l;--i) Mx[i]=max(Mx[i+1],a[i]),Mn[i]=min(Mn[i+1],a[i]);//預處理左區間字尾最值 for(i=mid+2;i<=r;++i) Mx[i]=max(Mx[i-1],a[i]),Mn[i]=min(Mn[i-1],a[i]);//預處理右區間字首最值 for(i=l;i<=mid;++i) p=i+Mx[i]-Mn[i],mid<p&&p<=r&&Mn[i]<Mn[p]&&Mx[p]<Mx[i]&&++ans;//最值都在左區間 for(i=mid+1;i<=r;++i) p=i-Mx[i]+Mn[i],l<=p&&p<=mid&&Mn[i]<Mn[p]&&Mx[p]<Mx[i]&&++ans;//最值都在右區間 for(i=mid,p=q=mid+1;i>=l;ans+=c[Mn[i]-i+n],--i)//最小值在左區間,最大值在右區間 {W(q<=r&&Mn[q]>Mn[i]) ++c[Mx[q]-q+n],++q;W(p^q&&Mx[p]<Mx[i]) --c[Mx[p]-p+n],++p;}//p,q維護合法右端點區間 W(p^q) --c[Mx[p]-p+n],++p;//清空 for(i=mid,p=q=mid+1;i>=l;ans+=c[Mx[i]+i],--i)//最大值在左區間,最小值在右區間 {W(q<=r&&Mx[q]<Mx[i]) ++c[Mn[q]+q],++q;W(p^q&&Mn[p]>Mn[i]) --c[Mn[p]+p],++p;} W(p^q) --c[Mn[p]+p],++p;//清空 } int main() { RI i,x,y;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&x,&y),a[x]=y;//二維化一維 return Solve(1,n),printf("%lld\n",ans),0; }