CF1175F The Number of Subpermutations(单调栈,ST表)
给出一个数组\(a\),若子串\(a[l,r]\)满足\([1,r-l+1]\)各出现一次,则称其为这个数组的一个子排列。
求这个数组的子排列个数。
做法:
观察到一个子区间\([l,r]\)是一个排列,等价于:
\([l,r]\)没有重复的数字。
\([l,r]\)中的最大值为\(r-l+1\)。
用单调栈处理出每个数作为最大值的左右端点。
那么以这个数为最大值的区间长度是固定的,且这个区间得覆盖这个数这个位置。
可以证明,这类区间的数量取决于这个数左右区间的较短的那个,所以暴力枚举覆盖这个数并以这个数为最大值的区间即可。
然后,如何判断一个区间没有重复的数字?
问题转化为这个区间内是否有一个数字出现了两次。
对每个数维护一个前驱出现位置,对这个区间询问前驱出现位置的最小值即可。
如果最小值>=l,说明有数字出现了两次。
查询最小值上ST表即可。
时间复杂度\(O(nlogn)\)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
int n,a[maxn];
stack<int> st;
int L[maxn],R[maxn],pre[maxn],mp[maxn];
int dp[maxn][20],mm[maxn];
void initRMQ (int n) {
mm[0]=-1;
for (int i=1;i<=n;i++) {
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
dp[i][0]=pre[i];
}
for (int j=1;j<=mm[n];j++) {
for (int i=1;i+(1<<j)-1<=n;i++) {
dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
}
}
int rmq (int x,int y) {
int k=mm[y-x+1];
return max(dp[x][k],dp[y-(1<<k)+1][k]);
}
int main () {
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",a+i);
for (int i=1;i<=n;i++) L[i]=0,R[i]=n+1;
for (int i=1;i<=n;i++) {
while (st.size()) {
int u=st.top();
if (a[u]>=a[i]) {
L[i]=u;
break;
}
st.pop();
}
st.push(i);
}
while (st.size()) st.pop();
for (int i=n;i>=1;i--) {
while (st.size()) {
int u=st.top();
if (a[u]>a[i]) {
R[i]=u;
break;
}
st.pop();
}
st.push(i);
}
for (int i=1;i<=n;i++) {
if (!mp[a[i]]) {
mp[a[i]]=i;
pre[i]=0;
}
else {
pre[i]=mp[a[i]];
mp[a[i]]=i;
}
}
initRMQ(n);
int ans=0;
for (int i=1;i<=n;i++) {
// printf("%d %d %d\n",L[i]+1,R[i]-1,a[i]);
int x=i-L[i];
int y=R[i]-i;
int len=a[i];
if (x<y) {
for (int j=L[i]+1;j<=i;j++) {
if (j+len-1<i||j+len-1>=R[i]) continue;
int l=j,r=j+len-1;
if (rmq(l,r)<l) {
// printf("%d %d\n",l,r);
ans++;
}
}
}
else {
for (int j=i;j<=R[i]-1;j++) {
if (j-len+1<=L[i]||j-len+1>i) continue;
int l=j-len+1,r=j;
if (rmq(l,r)<l) {
// printf("%d %d\n",l,r);
ans++;
}
}
}
}
printf("%d\n",ans);
}