hdoj6701 Make Rounddog Happy(单调栈维护区间最大+分治)
传送:http://acm.hdu.edu.cn/showproblem.php?pid=6701
题意:
给定一个长度为$n$的数组,问存在多少个子序列可以满足:$max(a_l,a_{l+1},...,a_r-(r-l+1)<=k$。同时要求子序列内数字不重复。
数据范围:
$1<=n<=3e5,1<=a_i<=n$。
分析:
首先考虑不重复怎么做呢?
很容易想到用单调栈维护当前$a_i$作为最大值可以扩展到的左界和右界。$L[i],R[i]$。
然后考虑怎么重复数字呢?然后就可以想到,一个数作为左界(右界)向右(左)不重复数字最长到达的位置。$L2[i],R2[i]$。
然后分治考虑答案。对于每个$a_i$作为最大值,找边界!
拿左侧或者右侧元素较少的一侧进行枚举。
假设左侧元素更少:枚举左界,然后判断右界。同理去处理右界。
然后对于每个情况累加答案即可。
有一个证明复杂度的博客:https://blog.csdn.net/qq_41955236/article/details/99990149
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn=3e5+10; 5 int a[maxn],L[maxn],L2[maxn],R[maxn],R2[maxn],q[maxn],num[maxn],len[maxn]; 6 int main(){ 7 // freopen("in.txt","r",stdin); 8 // freopen("out.txt","w",stdout); 9 int t,n,k;scanf("%d",&t); 10 while (t--){ 11 scanf("%d%d",&n,&k); 12 for (int i=1;i<=n;i++) scanf("%d",&a[i]),len[i]=max(1,a[i]-k); 13 int top=0; 14 for (int i=1;i<=n;i++){ 15 while (top && a[q[top]]<a[i]) top--; 16 if (top==0) L[i]=1; else L[i]=q[top]+1; 17 q[++top]=i; 18 } 19 top=0; 20 for (int i=n;i>=1;i--){ 21 while (top && a[q[top]]<a[i]) top--; 22 if (top==0) R[i]=n; else R[i]=q[top]-1; 23 q[++top]=i; 24 } 25 for (int i=1;i<=n;i++) num[i]=0;top=0; 26 for (int i=1;i<=n;i++){ 27 while (top<n && num[a[top+1]]==0){ 28 top++; num[a[top]]++; 29 } 30 L2[i]=top; 31 num[a[i]]--; 32 } 33 for (int i=1;i<=n;i++) num[i]=0;top=n+1; 34 for (int i=n;i>=1;i--){ 35 while (top>1 && num[a[top-1]]==0){ 36 top--; num[a[top]]++; 37 } 38 R2[i]=top; 39 num[a[i]]--; 40 } 41 ll ans=0; 42 for (int i=1;i<=n;i++){ 43 if (i-L[i]+1<=R[i]-i+1){ 44 for (int j=L[i];j<=i;j++){ 45 if (L2[j]<i) continue; 46 int rr=min(L2[j],R[i]);//右界 47 int nn=i-j+1; //a[i]左侧的长度 48 if (len[i]<=nn) ans+=rr-i+1; 49 else ans+=max(0,rr-a[i]+k-j+2); 50 } 51 } 52 else{ 53 for (int j=i;j<=R[i];j++){ 54 if (R2[j]>i) continue; 55 int ll=max(R2[j],L[i]);//左界 56 int mm=j-i+1; //a[i]右侧的长度 57 if (len[i]<=mm) ans+=i-ll+1; 58 else ans+=max(0,j+1-a[i]+k-ll+1); 59 } 60 } 61 } 62 printf("%lld\n",ans); 63 } 64 return 0; 65 }