给出一个数列,求一个区间不相同数字的个数,包含修改操作——最多1000次修改;数字最大是100W
用pre[i]记录前一个和i相同颜色的球的所在位置
询问l到r时,如果pre[i]<l说明在l到i这一段没用和i颜色相同的球,则ans++
利用这种思路我们可以。。。分块
每一块内按pre[i]排序,然后分块做就行了
不过正解是树套树,主流的思路还是一样,就是通过pre来做,只不过用不同数据结构维护罢了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
using namespace std;
const int N=12005;
int n,m,a[N],c[N],block,num;
int pre[N],head[N*100],pos[N];
void build()
{
block=sqrt(n);
num=block+ (block*block!=n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/block+1;
for (int i=1;i<=n;i++)
{
a[i]=pre[i]=head[c[i]];
head[c[i]]=i;
}
for (int i=1;i<=num;i++)
{
int l=(i-1)*block+1,r=min(i*block,n);
sort(a+l,a+r+1);
}
}
int find(int x,int v)
{
int l=(x-1)*block+1,r=min(x*block,n);
int first=l;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid]<v)l=mid+1;
else r=mid-1;
}
return l-first;
}
void query()
{
int l,r,ans=0;
scanf("%d%d",&l,&r);
if (pos[l]==pos[r]) for (int i=l;i<=r;i++) ans+=(pre[i]<l);
else
{
for (int i=l;i<=min(pos[l]*block,n);i++) ans+=(pre[i]<l);//这里是pre,不能是a,因为a数组是按块排序以后的不是原数组,只是便于询问的辅助的数组。
for (int i=(pos[r]-1)*block+1;i<=r;i++) ans+=(pre[i]<l);
for (int i=pos[l]+1;i<=pos[r]-1;i++)
{
int ll=(i-1)*block+1,rr=min(i*block,n);
ans+=lower_bound(a+ll,a+rr+1,l)-a-ll;
}
}
printf("%d\n",ans);
}
void up(int i)
{
int ll=(i-1)*block+1,rr=min(i*block,n);
for (int i=ll;i<=rr;i++) a[i]=pre[i];
sort(a+ll,a+rr+1);
}
void updata()
{
int x,k;
scanf("%d%d",&x,&k);
for (int i=1;i<=n;i++) head[c[i]]=0;
c[x]=k;
for (int i=1;i<=n;i++)
{
int tmp=pre[i];
pre[i]=head[c[i]];
head[c[i]]=i;
if (tmp!=pre[i]) up(pos[i]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&c[i]);
build();
char ch[2];
while (m--)
{
scanf("%s",ch);
if (ch[0]=='Q') query();
else updata();
}
return 0;
}
总结
1:注意在用stl的时候,一定要注意是左闭右开区间,即右端点要加一,才能成为右开(这里wa了好几次)
2:这里是用pos【i】,比较主流的写法。
3:注意当最后一个区间不是整区间时,右端点取n,不是i*block,就是在取区间的时候注意右端点取min。
4:这里通过pre【i】维护上一个数出现的位置,来处理不相同的个数。思路真的好巧妙,然后因为更新次数少,所以单次更新的复杂度可以高一点,也就是分块有的时候处理还是很暴力的(这里每次更新有一些都要重新计算)。