2019杭电多校第二场Longest Subarray HDU - 6602(线段树,数字贡献度)

Longest Subarray HDU - 6602
题意:一段长度为n的序列,每个元素都的值域为 [ 1 , c ] [1,c] [1,c],给出一个参数k,现在要找到一个最大的区间,使得区间内的某种数字要么出现了 > = k >=k >=k次,要么出现了0次。
∀ x ∈ [ 1 , c ] , ∑ i = l r [ a i = x ] = 0 o r ∑ i = l r [ a i = x ] > = k {\forall}x\in[1,c], \sum_{i=l}^r[a_i=x]=0 or\sum_{i=l}^r[a_i=x]>=k x[1,c],i=lr[ai=x]=0ori=lr[ai=x]>=k
输出最大的区间长度。

线段树维护区间左端点的包含的数字的贡献。
从左往右依次看每个数字的贡献,也即选定当前扫描的位置作为右端点,查看最远的合法左端点。
如果 k < = 1 k<=1 k<=1,区间直接就是n了是把。
考虑 k > = 2 k>=2 k>=2的情况
考虑只取一个点,那么是不是除了这个点,其他点均出现了0次,也就是说其他数字对这个点的贡献为c-1。
当一个数字x出现了刚好k次的时候,是不是只要区间左端点取 [ 1 , x F i r s t P o s ] [1,xFirstPos] [1,xFirstPos]就可以保证x出现了k次,那同理,当x再下一次出现的时候,区间左端点的合法区段就会扩大,扩大的部分为: [ x F i r s t P o s + 1 , x S e c o n d P o s ] [xFirstPos+1,xSecondPos] [xFirstPos+1,xSecondPos]对吧,那么是不是数字x会对这些小段的区间产生1的贡献。

那同样的考虑出现0次,当x第一次出现在pos位置的时候,是不是说明右端点为pos的话, [ 1 , p o s ] [1,pos] [1,pos]都是不合法的位置对不对,因为右端点我们已经确定为了pos,不管左端点怎么选,都会包含这个数字,这个数字只出现了一次是不对的。所以数字x对于这一段区间的贡献为-1。
也就是说我们不断的执行区间加减1操作,每次查询合法的最前面左端点(合法的左端点当然就是满足该点值为c啦)。
预处理出所有数字的位置,最好放一个0进去,后面写的时候就可以少写一些if else语句。

#include
using namespace std;

const int maxn=1e5+7;
typedef long long ll;

int sum[maxn<<2|1];
int lazy[maxn<<2|1];
int num[maxn];
vector<int> v[maxn];
int c;

void pushup(int k){
    sum[k]=max(sum[k<<1],sum[k<<1|1]);
}

void build(int l,int r,int k){
    lazy[k]=sum[k]=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(l,mid,k<<1);
    build(mid+1,r,k<<1|1);
}

void pushdown(int k){
    if(lazy[k]){
        lazy[k<<1]+=lazy[k];
        lazy[k<<1|1]+=lazy[k];
        sum[k<<1]+=lazy[k];
        sum[k<<1|1]+=lazy[k];
        lazy[k]=0;
    }
}

void updata(int l,int r,int k,int L,int R,int val){
    if(L>R) return ;
    if(l>=L&&r<=R){
        sum[k]+=val;
        lazy[k]+=val;
        return ;
    }
    int mid=(l+r)>>1;
    pushdown(k);
    if(L<=mid) updata(l,mid,k<<1,L,R,val);
    if(R>mid) updata(mid+1,r,k<<1|1,L,R,val);
    pushup(k);
}

int myfind(int l,int r,int k,int x){
    if(l==r){
        return sum[k]==x?l:maxn;
    }
    pushdown(k);
    int mid=(l+r)>>1;
    int res=maxn;
    if(sum[k<<1]>=x) res=myfind(l,mid,k<<1,x);
    else if(sum[k<<1|1]>=x) res=myfind(mid+1,r,k<<1|1,x);
    pushup(k);
    return res;
}

int a[maxn];

int main(){
    int n,k,x;
    while(scanf("%d%d%d",&n,&c,&k)!=EOF){
        if(k<=1){
            for(int i=1;i<=n;++i) scanf("%d",&x);
            printf("%d\n",n);
            continue;
        }
        for(int i=1;i<=c;++i) v[i].push_back(0);
        build(1,n,1);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            v[a[i]].push_back(i);
        }

        int res=0;
        for(int i=1;i<=n;++i){
            ++num[a[i]];

            //左端点选择i的话,只有a[i]出现了一次,其他数字都是0次,所以其他数字对于左端点为i时的总贡献为c-1;
            updata(1,n,1,i,i,c-1);

            //>=k的贡献,合法区间;
            if(num[a[i]]>=k) updata(1,n,1,v[a[i]][num[a[i]]-k]+1,v[a[i]][num[a[i]]-k+1],1);

            //不合法区间;
            //如果左端点选择下面的区间,必定会包含a[i],且只包含了一次,与>=k||==0冲突;
            updata(1,n,1,v[a[i]][num[a[i]]-1]+1,i-1,-1);

            int x=myfind(1,n,1,c);
            //cout<
            res=max(res,i-x+1);
        }

        printf("%d\n",res);
        for(int i=1;i<=c;++i) v[i].clear(),num[i]=0;
    }
}

你可能感兴趣的:(线段树)