链接: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;
}