題意:
給定長度為 \(N\) 的序列 \(a\),求滿足以下條件的 \((l,r)\) 對數:
-
\(1\le l\le r\le N\);
-
\(a_l,a_{l+1},\cdots,a_{r-1},a_r\) 是 \(1\sim r-l+1\) 的排列。
-
\(1\le N\le 10^6\);\(1\le a_i\le N\)。
思路
-
首先,“排列”本身這個性質是很強的。因為排列本身需要從1開始,因此排列的數目必定不會很多。
同時,只要我們知道了排列中最大的數,我們就知道了這個排列的長度。 -
因此考慮去找區間中最大的數,然後去列舉區間的範圍。在這個區間中,我們其實就將問題轉化為了區間內的數的種類。只不過這裡種類必須為區間長度。
這種問題有一個很經典的轉化,即維護一個數上一個與其值相同的數出現的位置,然後線段樹去統計。
不過這道題並不需要,因為我們只需要判斷種類數是不是 \(n\) 就行了,因此可以用st表 \(O(1)\) 判斷,只需要區間內所有數上一次出現的位置都小於區間左端點就行了。 -
處理完最大值後就可以繼續向兩邊遞迴去找。
複雜度
- 這裡的時間複雜度與啟發式合併比較類似,平攤下來總體是 \(O(nlogn)\) 的。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,a[N],g[N][20],lst[N],lg[20];long long ans=0;
struct node
{
int val,loc;
}f[N][20];
node maxx(node x,node y){return x.val>=y.val?x:y;}
void init()
{
lg[0]=-1;for(int i=1;i<=n;i++) lg[i]=lg[i/2]+1;
for(int i=1;(1<<i)<=n;i++)for(int j=1;j+(1<<i)-1<=n;j++)
f[j][i]=maxx(f[j][i-1],f[j+(1<<(i-1))][i-1]),g[j][i]=max(g[j][i-1],g[j+(1<<(i-1))][i-1]);
}
node fmax(int l,int r){int x=lg[r-l+1];return maxx(f[l][x],f[r-(1<<x)+1][x]);}
int gmax(int l,int r){int x=lg[r-l+1];return max (g[l][x],g[r-(1<<x)+1][x]);}
bool query(int l,int r){return gmax(l,r)<l;}
void solve(int l,int r)
{
if(r<l) return;if(l==r) {ans+=(a[l]==1);return;}
node mval=fmax(l,r);
if(mval.loc-l<=r-mval.loc)
for(int i=max(l,mval.loc-mval.val+1),j=i+mval.val-1;i<=mval.loc&&j<=min(r,mval.loc+mval.val-1);++i,++j) ans+=query(i,j);
else for(int j=mval.loc,i=j-mval.val+1;i<=mval.loc,j<=min(r,mval.loc+mval.val-1);++i,++j) if(i<l) continue;else ans+=query(i,j);
solve(l,mval.loc-1),solve(mval.loc+1,r);
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;for(int i=1;i<=n;i++) {cin>>a[i];f[i][0]={a[i],i},g[i][0]=lst[a[i]],lst[a[i]]=i;}
init();solve(1,n);cout<<ans<<'\n';
return 0;
}