JXOI2017 颜色
颜色
可怜有一个长度为 \(n\) 的正整数序列 \(A_i\),其中相同的正整数代表着相同的颜色。
现在可怜觉得这个序列太长了,于是她决定选择一些颜色把这些颜色的所有位置都删去。删除颜色 \(i\) 可以定义为把所有满足 \(A_j = i\) 的位置 \(j\) 都从序列中删去。
然而有些时候删去之后,整个序列变成了好几段,可怜不喜欢这样,于是她想要知道有多少种删去颜色的方案使得最后剩下来的序列非空且连续。
例如颜色序列 \([1, 2, 3, 4, 5]\),删除颜色 \(3\) 后序列变成了 \([1, 2]\) 和 \([4, 5]\) 两段,不满足条件。而删除颜色 \(1\) 后序列变成了 \([2, 3, 4, 5]\),满足条件。
两个方案不同当且仅当至少存在一个颜色 \(i\) 只在其中一个方案中被删去。
对于 \(100\%\) 的数据,保证 \(1 \leq T, ∑n \leq 3 \times 10^5 , 1 \leq A_i \leq n\)。
随机化
对于每个位置赋一个 \([0,2^{64})\) 之间的随机值,使得所有颜色相同位置上的值异或和为 \(0\)。
那么合法区间一定异或和为\(0\),考虑不合法区间异或和为\(0\)的概率,应该是\(2^{-64}\)。
用map统计即可,时间复杂度\(O(n\log n)\)。
IN uint64 gen(){
return rand()^rand()<<15^(uint64)rand()<<30^(uint64)rand()<<45^(uint64)rand()<<60;
}
CO int N=3e5+10;
int a[N];
vector<int> pos[N];
uint64 val[N];
map<uint64,int> cnt;
void real_main(){
int n=read<int>();
for(int i=1;i<=n;++i) pos[i].clear();
for(int i=1;i<=n;++i) pos[read(a[i])].push_back(i);
for(int i=1;i<=n;++i)if(pos[i].size()){
uint64 sum=0;
for(int j=0;j<(int)pos[i].size()-1;++j)
val[pos[i][j]]=gen(),sum^=val[pos[i][j]];
val[pos[i].back()]=sum;
}
int64 ans=0;
cnt.clear(),cnt[0]=1;
uint64 sum=0;
for(int i=1;i<=n;++i){
sum^=val[i];
ans+=cnt[sum];
++cnt[sum];
}
printf("%lld\n",ans);
}
int main(){
srand(20030506);
for(int T=read<int>();T--;) real_main();
return 0;
}
线段树
合法的删颜色方案与合法的剩余区间一一对应,所以考虑算合法区间的数量。
区间数量的级别是\(O(n^2)\)的,直接枚举不可行,考虑使用套路:枚举\(r\),线段树维护合法的\(l\)。
需要考虑两个条件:
-
\([r+1,n]\)中的颜色都被删除了。对于某个颜色,我们关心它出现的位置中\(\leq i\)的最大的是多少。将所有的最大位置取\(\max\)得到\(p\),那么\(l\in [p+1,r]\)。
-
比如对于颜色序列\([1,3,2,3,5]\),\(r=5\)时\(l\)不能取\(3,4\)。这些颜色一定都出现完毕了,那么在线段树上区间赋值\(0\)即可。
时间复杂度\(O(n\log n)\)。
CO int N=3e5+10;
int col[N];
vector<int> pos[N];
set<int> lst;
int tree[4*N],tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void push_up(int x){
tree[x]=tree[lc]+tree[rc];
}
IN void push_down(int x){
if(tag[x]){
tree[lc]=0,tag[lc]=1;
tree[rc]=0,tag[rc]=1;
tag[x]=0;
}
}
void build(int x,int l,int r){
tag[x]=0;
if(l==r) {tree[x]=1; return;}
build(lc,l,mid),build(rc,mid+1,r);
push_up(x);
}
void modify(int x,int l,int r,int ql,int qr){
if(ql>qr) return;
if(ql<=l and r<=qr) {tree[x]=0,tag[x]=1; return;};
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr);
if(qr>mid) modify(rc,mid+1,r,ql,qr);
push_up(x);
}
int query(int x,int l,int r,int ql,int qr){
if(ql>qr) return 0;
if(ql<=l and r<=qr) return tree[x];
push_down(x);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
void real_main(){
int n=read<int>();
for(int i=1;i<=n;++i) pos[i].clear();
for(int i=1;i<=n;++i) pos[read(col[i])].push_back(i);
int64 ans=0;
lst.clear(),lst.insert(0);
build(1,1,n);
for(int i=1;i<=n;++i){
if(i!=pos[col[i]].front())
lst.erase(*--lower_bound(pos[col[i]].begin(),pos[col[i]].end(),i));
lst.insert(i);
if(i==pos[col[i]].back()){
lst.erase(i);
modify(1,1,n,pos[col[i]].front()+1,i);
}
ans+=query(1,1,n,*lst.rbegin()+1,i);
}
printf("%lld\n",ans);
}
int main(){
for(int T=read<int>();T--;) real_main();
return 0;
}