链接:HDU6701 Make Rounddog Happy
题意:
给出长度为 n    ( ≤ 300000 ) n\;(\le 300000) n(≤300000)的序列 a 1 , a 2 , ?   , a n    ( 1 ≤ a i ≤ n ) a_1,a_2,\cdots,a_n\;(1\le a_i\le n) a1?,a2?,?,an?(1≤ai?≤n),求出有多少满足下列条件的连续子序列 a l , a l + 1 , ?   , a r a_l,a_{l+1},\cdots,a_r al?,al+1?,?,ar??
- 1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1≤l≤r≤n
- max ? ( a l , a l + 1 , ?   , a r ) ? ( r ? l + 1 ) ≤ k \max(a_l,a_{l+1},\cdots,ar)?(r?l+1)≤k max(al?,al+1?,?,ar)?(r?l+1)≤k
- ? l ≤ i < j ≤ r ,    a i ≠ a j \forall l\le i\lt j\le r,\;a_i\neq a_j ?l≤i<j≤r,ai???=aj?
分析:
由于满足条件的子序列内不能有重复元素,先预处理得到
- L [ i ] : L[i]: L[i]:以 a i a_i ai?结尾的子序列的最远开头;
- R [ i ] : R[i]: R[i]:以 a i a_i ai?开头的子序列的最远结尾。
设序列长度为 l e n = r ? l + 1 len=r-l+1 len=r?l+1,则条件 max ? ( a l , a l + 1 , ?   , a r ) ? ( r ? l + 1 ) ≤ k \max(a_l,a_{l+1},\cdots,ar)?(r?l+1)≤k max(al?,al+1?,?,ar)?(r?l+1)≤k即可转化为:
l e n ≥ max ? ( a l , a l + 1 , ?   , a r ) ? k len\ge \max(a_l,a_{l+1},\cdots,ar)-k len≥max(al?,al+1?,?,ar)?k
所以可以枚举最大值 a m a x a_{max} amax?,找到对应的区间,然后利用 L [ i ] L[i] L[i]和 R [ i ] R[i] R[i]在区间内计算符合的子序列数量。
关于枚举,需采用启发式分治,在分治过程中每次查找区间的最大值及其位置 p p p(时间复杂度 O ( log ? n ) O(\log n) O(logn)),每次分治以 p p p为分界遍历更短的区间,计算符合的子序列数量,这样共需要遍历: n 2 + n 4 + n 4 + n 8 + n 8 + n 8 + n 8 + . . . \frac{n}{2}+\frac{n}{4}+\frac{n}{4}+\frac{n}{8}+\frac{n}{8}+\frac{n}{8}+\frac{n}{8}+... 2n?+4n?+4n?+8n?+8n?+8n?+8n?+...个数,则时间复杂度为 O ( n log ? n ) O(n\log n) O(nlogn)。
所以总的时间复杂度为 O ( n log ? n ) O(n\log n) O(nlogn)。
以下代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn=3e5+10;
int n,k,a[maxn];
int pre[maxn],L[maxn],R[maxn];
int t[maxn<<2];
void push_up(int rt)
{
if(a[t[rt<<1]]>a[t[rt<<1|1]])t[rt]=t[rt<<1];elset[rt]=t[rt<<1|1];
}
void build(int rt,int l,int r)
{
if(l==r){
t[rt]=l;return;}int mid=(l+r)>>1;build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return t[rt];if(l>qr||r<ql)return 0;int mid=(l+r)>>1;int x=query(rt<<1,l,mid,ql,qr),y=query(rt<<1|1,mid+1,r,ql,qr);if(a[x]>a[y])return x;elsereturn y;
};
void prework()
{
memset(pre,0,sizeof(pre));L[0]=1;for(int i=1;i<=n;i++){
if(pre[a[i]])L[i]=max(L[i-1],pre[a[i]]+1);elseL[i]=L[i-1];pre[a[i]]=i;}memset(pre,0,sizeof(pre));R[n+1]=n;for(int i=n;i>=1;i--){
if(pre[a[i]])R[i]=min(R[i+1],pre[a[i]]-1);elseR[i]=R[i+1];pre[a[i]]=i;}build(1,1,n);
}
LL ans;
void solve(int l,int r)
{
if(l>r)return;int p=query(1,1,n,l,r),mid=(l+r)>>1; //找到区间a[l]~a[r]中最大值的位置int max_len,min_len;if(p<=mid) //遍历更短的区间{
for(int i=l;i<=p;i++){
if(R[i]<p)continue;max_len=min(R[i],r)-i+1; //以a[i]开头,a[p]为最大值,子序列可达到的最大长度min_len=max(1,max(a[p]-k,p-i+1)); //以a[i]开头,a[p]为最大值,子序列符合要求的最小长度if(max_len>=min_len)ans+=1LL*(max_len-min_len+1);}}else{
for(int i=r;i>=p;i--){
if(L[i]>p)continue;max_len=i-max(L[i],l)+1; //以a[i]结尾,a[p]为最大值,子序列可达到的最大长度min_len=max(1,max(a[p]-k,i-p+1)); //以a[i]结尾,a[p]为最大值,子序列符合要求的最小长度if(max_len>=min_len)ans+=1LL*(max_len-min_len+1);}}solve(l,p-1);solve(p+1,r);
}
int main()
{
int T;scanf("%d",&T);while(T--){
scanf("%d %d",&n,&k);for(int i=1;i<=n;i++)scanf("%d",&a[i]);prework();ans=0;solve(1,n);printf("%lld\n",ans);}return 0;
}