杭电2019多校第二场 HDU-6602 Longest Subarray(线段树+lazy标记)

链接:http://acm.hdu.edu.cn/showproblem.php?pid=6602

题意:多组样例。先给出n,k,c。接下来一行n个数(1<=a[i]<=c)。问最大的区间长度,对于 (1<=i<=c),这c个数,要么其个数为0,要么其个数>=k。

思路:

官方题解:如果右端点固定,对于每种元素,可行的左端点下标是两段连续的区间。对于每种元素,将它的可行左端点区间在线段树中加一。当右端点右移的时候,维护C 种元素的可行左端点。查询时只需要询问线段树中最小的、值为C 的下标即可。

详情请看图片和注释:

 

 

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N],n,c,k,ans,ll;
vector<int> ve[N];
struct node
{
    int l,r,val,lazy;
    //val:表示在[l,r]区间内,有多少个数符合要求 
	//lazy:区间更新,lazy标记,记下变化量 
}tree[N<<2];
void Init()
{
    for(int i=1;i<=c;i++)
        ve[i].clear();
    return ;
}
void pushup(int cur)
{
    tree[cur].val=max(tree[cur<<1].val,tree[cur<<1|1].val);
    return ;
}
void build(int l,int r,int cur)
{
    tree[cur].l=l;
    tree[cur].r=r;
    tree[cur].lazy=tree[cur].val=0;
    if(l==r)
    {
    	//如果是第一个,那么个数肯定是c-1
		//因为下面枚举右端点是从2开始的,这里特别处理 
        if(l==1)
            tree[cur].val=c-1;
        return;
    }
    int m=(l+r)>>1;
    build(l,m,cur<<1);
    build(m+1,r,cur<<1|1); 
    pushup(cur);
    return;
}

//有lazy标记,要向下更新 
void pushdown(int cur)
{
    if(tree[cur].lazy)
    {
        tree[cur<<1].lazy+=tree[cur].lazy;
        tree[cur<<1].val+=tree[cur].lazy;
         
        tree[cur<<1|1].lazy+=tree[cur].lazy;
        tree[cur<<1|1].val+=tree[cur].lazy;
         
        tree[cur].lazy=0;
    }
    return ;
}
void update(int l,int r,int cur,int val)
{
    if(l<=tree[cur].l&&tree[cur].r<=r)
    {
        tree[cur].val+=val;
        tree[cur].lazy+=val;
        return ;    
    }
    pushdown(cur);
    if(l<=tree[cur<<1].r) update(l,r,cur<<1,val);
    if(r>=tree[cur<<1|1].l) update(l,r,cur<<1|1,val);
    pushup(cur);
    return ;
}
void query(int l,int r,int cur)
{
	
    if(tree[cur].l==tree[cur].r)
    {
    	//cout<<ll<<endl;
        ll=tree[cur].l;
        return ;
    }
    //lazy标记,询问时也要pushdown 
    pushdown(cur);
    //因为要找最小的可行左端点,左子树如果符合条件, 
	//直接查找左子树,因为左子树的l小 
    if(tree[cur<<1].val==c) query(l,r,cur<<1);
    //否则,就要判断右子树符不符合条件和是不是包含在询问的[l,r]区间
	//符合并包含,就递归的询问右子树 
    else if(tree[cur<<1|1].l<=r&&tree[cur<<1|1].val==c) query(l,r,cur<<1|1);
    return;
}
 
int main(void)
{
    int pos,l0,r0,lk,rk,num;
    while(~scanf("%d%d%d",&n,&c,&k))    
    {
        Init();
        ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            ve[a[i]].push_back(i);
        }
        build(1,n,1);
        //如果k=1,那么要把[1,1]这个区间的val+1
		//因为建树的时候是c-1,现在k=1,那么a[1]的个数
		//在[1,1]这个区间内为1(==k)符合要求。 
        if(k==1)
        {
            update(1,1,1,1);
            ans=1;
        }
        for(int i=2;i<=n;i++)
        {
			//在[i,i]这个区间内,符合要求的数肯定是>=c-1个
			//因为这个区间内肯定有a[i]这个数,剩下的c-1个数,数目都为0,符合要求 
			//如果给出的k==1,那么符合要求的数就是c个,在下面会更新到。 
            update(i,i,1,c-1);
			 
            //计算i位置上的a[i]的下标,也就是他是第num=pos+1个a[i],因为下标是从0开始的,所以+1。 
            pos=lower_bound(ve[a[i]].begin(),ve[a[i]].end(),i)-ve[a[i]].begin();
            num=pos+1;
            
            //首先,把[1,i-1]这个区间内,a[i]个数为0的区间的val-1。 
			//[1,i]区间内最右边的a[i]的位置就是i。 
			//如果i位置上的a[i]是第一个a[i],那么区间左端点就是1,否则就是上一个a[i]的位置+1。 
            if(pos==0) l0=1;    
            else l0=ve[a[i]][pos-1]+1;
            //右端点肯定是i-1 
            r0=i-1;
			//把这个区间的val-1,即把原先a[i]个数为0的区间减去。 
            update(l0,r0,1,-1);
            
			//如果i位置上这个a[i],是第大于等于k个a[i]
			//那么现在要把右往左数,第k-1个a[i]和第k个a[i]这个区间的val+1,
			//即把原先a[i]个数为k-1的区间加上。 
            if(num>=k)
            {
            	//处理num==k的情况 
                if(pos-k<0) lk=1;
                else lk=ve[a[i]][pos-k]+1;
                
                rk=ve[a[i]][pos-k+1];
                update(lk,rk,1,1);
            }       
            ll=0;
            query(1,i,1);
            if(ll!=0) ans=max(ans,i-ll+1);
        }
        printf("%d\n",ans);
    }
     
     
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值