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]=0or∑i=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;
}
}