【2016北京集训】数组
Portal --> broken qwq
Description
给你一个数组,每个元素有一个颜色,要求支持两种操作:
1、修改某个元素的颜色
2、询问这个数组有多少个自取件内没有重复的颜色
数据范围:\(n<=10^5,m<=2n\),颜色大小在\(1\sim n\)之间
Solution
这题。。本来应该是一个树套树题
但是为什么一定要用树套树呢对吧qwq
首先是套路:考虑维护一个\(pre\)数组,表示每个节点的前一个最近的和它颜色一样的节点,那么我们考虑固定一个右端点,将左端点往左移,直到再往前移一位就会导致当前区间内的\(pre\)的最大值在这个区间内,这个时候区间的长度就是这个右端点对答案的贡献
然而。。直接这样做是不行的因为我们要支持修改和多组询问qwq
所以这里考虑用线段树维护一个神秘的东西:对于线段树中的一个节点,假设它对应的区间是\([l,r]\),那么我们维护一个\(mx\)和\(ans\),分别表示当前区间内\(pre\)的最大值,以及,只考虑当前区间的\(pre\)限制的右端点贡献之和(这个概念描述起来有点神秘,具体一点就是:首先这个\(ans\)记录的是该区间中的每个点作为右端点算得的贡献之和,但是这个贡献在计算的时候,只考虑当前区间内的\(pre\)的影响,更加直观一点来说就是可以理解为移动左端点求\(pre\)的最大值的时候,如果说新加进来的位置不在\([l,r]\)区间内,就不取\(max\)),然后答案就应该是线段树根节点的\(ans\)值了,对于叶子节点来说\(ans=x-pre[x]\),其中\(x\)是这个叶子节点在数组中对应的位置
现在考虑怎么维护这个东西
为了方便接下来的描述,约定用\(x\)表示当前区间,\(L\)表示当前区间的左儿子(左半部分),\(R\)表示当前区间的右儿子(右半部分),然后我们考虑怎么用\(ans[L]\)和\(ans[R]\)求得\(ans[x]\),这里需要根据\(mx[L]\)和\(mx[R]\)的大小关系进行一些讨论,首先我们先看最简单的情况:
(1)如果说\(mx[L]>=mx[R]\):首先\(ans[L]\)肯定还是会作为\(ans[x]\)的一部分的,因为加入的东西在后面,不会影响\(ans[L]\)的贡献,然后我们看\([mid+1,r]\)区间中的元素,因为\(mx[R]<=mx[L]\),也就是说后面的元素作为右端点的时候左端点停下的地方肯定在\(mx[L]+1\)这个位置,所以我们可以直接计算贡献:\(ans[x]=ans[L]+(mid-mx[L])*(r-mid)+\sum\limits_{i=1}^{r-mid}i\),具体一点的话就是\([mid+1,r]\)中的每个元素对应的左端点可以先走到\(mid+1\)这个位置,再走到\(mx[L]+1\)的位置
接下来看复杂一点的另一种情况:
(2)如果说\(mx[L]<mx[R]\),那么说明\([mid+1,r]\)中有一部分的元素对应的左端点可以走到\(mx[L]+1\),有的在\([mid+1,r]\)中的某个位置就停下了,这个时候我们就不能直接计算答案了,由于\(pre\)的\(max\)值在左端点的移动过程中是递增的,所以我们可以考虑递归求解,在递归求解的时候也是通过这个右儿子左儿子的\(mx\)值和\(mx[L]\)进行比较,如果可以直接计算答案就直接计算,否则继续递归,遇到叶子的话也是返回,因为只有一个节点了可以直接计算
最后就是\(pre\)的修改,我们只要对每个值用一个\(set\)随便维护一下下标位置就好啦
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+10,SEG=N*4;
int pre[N],a[N];
set<int> rec[N];
set<int>::iterator it,fr,nxt;
int n,m;
ll sum(int l,int r){return 1LL*(l+r)*(r-l+1)/2;}
namespace Seg{/*{{{*/
int ch[SEG][2],mx[SEG];
ll ans[SEG];
int n,tot;
ll calc(int x,int l,int r,int L){
int mid=l+r>>1;
if (l==r) return l-max(mx[L],mx[x]);
if (mx[ch[x][0]]<=mx[L])
return 1LL*(l-1-mx[L])*(mid-l+1)+sum(1,mid-l+1)+calc(ch[x][1],mid+1,r,L);
else
return (ans[x]-ans[ch[x][0]])+calc(ch[x][0],l,mid,L);
}
void pushup(int x,int l,int r){
int mid=l+r>>1;
mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]);
ans[x]=ans[ch[x][0]];
if (mx[ch[x][0]]>=mx[ch[x][1]])
ans[x]+=1LL*(mid-mx[ch[x][0]])*(r-mid)+sum(1,r-mid);
else
ans[x]+=calc(ch[x][1],mid+1,r,ch[x][0]);
}
void _build(int x,int l,int r){
mx[x]=0;
if (l==r){
mx[x]=pre[l]; ans[x]=l-pre[l];
return;
}
int mid=l+r>>1;
ch[x][0]=++tot; _build(ch[x][0],l,mid);
ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
pushup(x,l,r);
}
void build(int _n){n=_n; tot=1; _build(1,1,n);}
void _update(int x,int d,int lx,int rx){
if (lx==rx){
mx[x]=pre[lx]; ans[x]=lx-pre[lx];
return;
}
int mid=lx+rx>>1;
if (d<=mid) _update(ch[x][0],d,lx,mid);
else _update(ch[x][1],d,mid+1,rx);
pushup(x,lx,rx);
}
void update(int d){_update(1,d,1,n);}
void debug(int x,int l,int r){
if (l==r){printf("%lld ",ans[x]); return;}
int mid=l+r>>1;
debug(ch[x][0],l,mid);
debug(ch[x][1],mid+1,r);
}
void debug(){debug(1,1,n);}
}/*}}}*/
void update(int x,int delta){
it=rec[a[x]].find(x);
fr=it; nxt=it; --fr; ++nxt;
if (nxt!=rec[a[x]].end()){
pre[*nxt]=*fr;
Seg::update(*nxt);
}
rec[a[x]].erase(x);
a[x]=delta;
rec[a[x]].insert(x);
it=rec[a[x]].find(x);
fr=it; nxt=it; --fr; ++nxt;
if (nxt!=rec[a[x]].end()){
pre[*nxt]=x;
Seg::update(*nxt);
}
pre[x]=*fr;
Seg::update(x);
}
void debug(){
Seg::debug();
printf("\n");
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
int op,x,to;
scanf("%d",&n);
for (int i=1;i<=n;++i) rec[i].insert(0);
for (int i=1;i<=n;++i){
scanf("%d",a+i);
pre[i]=*(--rec[a[i]].end());
rec[a[i]].insert(i);
}
Seg::build(n);
scanf("%d",&m);
for (int i=1;i<=m;++i){
scanf("%d",&op);
if (op==0)
printf("%lld\n",Seg::ans[1]);
else{
scanf("%d%d",&x,&to);
update(x,to);
}
//debug();
}
}