题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6602
题目大意:给你一个串,问满足以下条件的子串中最长的是多长:对于每个数字,要么在这个子串没出现过,要么出现次数超过k次。
转:
思路:对于这类问题,常常转化为数据结构的询问问题。我们考虑枚举右端点,对于当前右端点,我们单独考虑每一种数的合法区间。假设当前枚举的右端点是i,考虑的数字是c,在右端点左边离i最近的数字c的位置是p1,离i第k远的数字c的位置是p2, 容易发现,数字c的合法区间为[1, p2]和[p1 + 1, i],对应的情况是选择这个数至少k个和不选这个数。那么,如果我们用线段树来维护覆盖的区间,对于每一种数的合法区间在线段树上+1,这样我们只要找到在i前面值为c的最小的位置就是右端点为i的最优解。由于每次右端点只移动1,所以可以在O(logn)时间内维护一个数合法区间的变化。最小位置的找法可以通过维护区间最大值然后在线段树上二分即可。
#include <bits/stdc++.h>
using namespace std;
struct Node
{
int L, R;
}node[405000];
int mx[400050], add[400050];
void BT(int i, int L, int R)
{
node[i].L=L, node[i].R=R;
mx[i]=0, add[i]=0;
if(L==R)
{
return ;
}
BT((i<<1), L, (L+R)/2);
BT((i<<1)+1, (L+R)/2+1, R);
}
void UPADD(int i)
{
if(add[i])
{
add[i<<1]+=add[i];
add[(i<<1)+1]+=add[i];
mx[i<<1]+=add[i];
mx[(i<<1)+1]+=add[i];
add[i]=0;
}
}
void UP(int i, int L, int R, int c)
{
if(node[i].L==L&&node[i].R==R)
{
add[i]+=c;
mx[i]+=c;
return ;
}
if(node[i].L==node[i].R)
{
return ;
}
UPADD(i);
int mid=(node[i].L+node[i].R)/2;
if(R<=mid)
{
UP(i<<1, L, R, c);
}
else if(L>mid)
{
UP((i<<1)+1, L, R, c);
}
else
{
UP(i<<1, L, mid, c);
UP((i<<1)+1, mid+1, R, c);
}
mx[i]=max(mx[i<<1],mx[(i<<1)+1]);
}
int cx(int i, int L, int R, int k)
{
if(L==R)
{
return L;
}
UPADD(i);
int mid=(L+R)/2;
if(mx[i<<1]==k)
{
return cx(i<<1, L, mid, k);
}
if(mx[(i<<1)+1]==k)
{
return cx((i<<1)+1, mid+1, R, k);
}
return -1;
}
int a[100050];
int main()
{
int n, c, k;
while(~scanf("%d%d%d", &n, &c, &k))
{
vector<int> pos[100050];
for(int i=1;i<=c;i++)
{
pos[i].push_back(0);
}
for(int i=1;i<=n;i++)
{
scanf("%d", &a[i]);
pos[a[i]].push_back(i);
}
if(k==1)
{
printf("%d\n", n);
continue;
}
BT(1, 1, n);
int ans=0, cur[100050]={0};
for(int i=1;i<=n;i++)
{
int x=a[i];
int p=++cur[x];
UP(1, i, i, c-1);
if(pos[x][p-1]+1<=pos[x][p]-1)
{
UP(1, pos[x][p-1]+1, pos[x][p]-1, -1);
}
if(p>=k)
{
UP(1, pos[x][p-k]+1, pos[x][p-k+1], 1);
}
int temp=cx(1, 1, n, c);
if(temp!=-1)
{
ans=max(ans, i-temp+1);
}
}
printf("%d\n", ans);
}
return 0;
}