2019多校第十场 HDU6701 Make Rounddog Happy(启发式分治)

链接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(1ain),求出有多少满足下列条件的连续子序列 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 1lrn
  • 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)(rl+1)k
  • ∀ l ≤ i &lt; j ≤ r , &ThickSpace; a i ≠ a j \forall l\le i\lt j\le r,\;a_i\neq a_j li<jr,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=rl+1,则条件 max ⁡ ( a l , a l + 1 , ⋯ &ThinSpace; , a r ) − ( r − l + 1 ) ≤ k \max(a_l,a_{l+1},\cdots,ar)−(r−l+1)≤k max(al,al+1,,ar)(rl+1)k即可转化为:
l e n ≥ max ⁡ ( a l , a l + 1 , ⋯ &ThinSpace; , a r ) − k len\ge \max(a_l,a_{l+1},\cdots,ar)-k lenmax(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];
    else
        t[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;
    else
        return 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);
        else
            L[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);
        else
            R[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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值