hdu 6602 Longest Subarray 想法线段树

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

 

题意:

        给你一个数组a,保证该数组中的每个数字属于[1,c],现在要你求一段最长的区间使得,在这个区间内,所有在1~c的数字要么不出现,要么出现超过k次。

做法:

        很有意思的一个线段树啊真不好想。

        假设我们枚举每一个右界R,那么对于在它左边的每一个出现的数字c,我们先找到它出现的第一个位置First_Pos_c和往前找到第k次出现的Kth_Pos_c,如果我们找到的区间要保证这个数字合法,那么我们找的L必须要在[1,Kth_Pos_c]和(First_Pos_c, R]之间,因为这样我们才能使得这个数字要么出现k次(前者),要么不出现(后者),这里注意我们区间的开闭关系。

         所以我们就可以这样,对于每一个数字,我们都保存下它所出现的位置,然后我们去一个个的更新。对于一个数字没有出现的区间,我们把它加上1,或者是,已经有多出k次出现的数字,我们把从1到第前k次的位置也加1(注意每个数字都要加上一个初始的区间0-n+1,这样一个数字如果没有出现的话也能把贡献加上),枚举到当前位置的时候,我们要把前一段它没有出现的位置减掉,把下一段它没有出现的位置加上(这样在枚举右区间的时候它的影响才能被算上),然后去看看最左边的现在有c个数字的位置在哪里,如果存在就把这个区间的长度和答案进行更新即可。


#include <bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn=100005;
int maxx[maxn<<2],laz[maxn<<2];
int n,c,k,pos[maxn],a[maxn];
vector<int> ve[maxn];
void deal(int rt,int v){
    maxx[rt]+=v;
    laz[rt]+=v;
}
void build(int l,int r,int rt){
    maxx[rt]=laz[rt]=0;
    if(l==r) return ;
    int mid=(r+l)/2;
    build(l,mid,lson);
    build(mid+1,r,rson);
}
void push_down(int rt){
    if(laz[rt]){
        deal(lson,laz[rt]);
        deal(rson,laz[rt]);
        laz[rt]=0;
    }
}
void update(int l,int r,int rt,int ql,int qr,int v){
    if(qr<ql) return ;
    if(ql<=l&&r<=qr){
        deal(rt,v);
        return ;
    }
    push_down(rt);
    int mid=(r+l)/2;
    if(ql<=mid) update(l,mid,lson,ql,qr,v);
    if(qr>mid) update(mid+1,r,rson,ql,qr,v);
    maxx[rt]=max(maxx[lson],maxx[rson]);
}
int query(int l,int r,int rt){
    if(l==r) return l;
    push_down(rt);
    int mid=(r+l)/2,ret=-1;
    if(maxx[lson]==c) ret=query(l,mid,lson);
    else if(maxx[rson]==c) ret=query(mid+1,r,rson);
    return ret;
}
void init(){
    rep(i,1,c){
        ve[i].clear();
        ve[i].push_back(0);
    }
    build(1,n,1);
}
int main(){
    while(~scanf("%d%d%d",&n,&c,&k)){
        init();
        rep(i,1,n) {
            scanf("%d",&a[i]);
            ve[a[i]].push_back(i);
        }
        rep(i,1,c) {
            ve[i].push_back(n+1);
            update(1,n,1,ve[i][0]+1,ve[i][1]-1,1);
            pos[i]=0;
        }
        int ans=0;
        for(int i=1;i<=n;i++){
            int aim=a[i];
            update(1,n,1,ve[aim][pos[aim]]+1,ve[aim][pos[aim]+1]-1,-1);
            if(pos[aim]>=k) update(1,n,1,1,ve[aim][pos[aim]-k+1],-1);
            pos[aim]++;
            update(1,n,1,ve[aim][pos[aim]]+1,ve[aim][pos[aim]+1]-1,1);
            if(pos[aim]>=k) update(1,n,1,1,ve[aim][pos[aim]-k+1],1);
            int tmp=query(1,n,1);
            if(tmp!=-1) ans=max(ans,i-tmp+1);
        }
        printf("%d\n",ans);

    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值