随机化写法很强,但是这里不说。
这里只记录数据结构写法。
枚举右端点,快速找左端点。
首先一种合法的方案中,一种颜色只会有两种情况。全部在区间中及全部在区间外。
对于区间外的情况,也就是最后一次出现的位置 \(p\) 大于右端点 \(r\),我们可以维护当前枚举右端点之前的所有颜色非最后一次出现的点的最大位置,但为了方便后续维护,我们对于每种颜色只维护最后一个该颜色非最后一次出现的点。这里可以用可删堆实现。即当遍历到一个点时,将上一个该颜色的点从堆中删除。
对于区间内的情况,即后一次出现的位置 \(p\) 小于等于右端点 \(r\),则这个颜色第一次出现的位置的下一个位置到当前右端点都不能作为区间的左端点,我们可以在这个区间打标记。
最后堆顶即为左端点,答案为左右端点距离减去中间被删去的点的数量。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define int long long
#define debug cout<<"DEBUG"<<endl;
#define pb push_back
#define pii pair<int,int>
#define vi vector<int>
#define imp map<int,int>
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
using namespace std;
const int N=1e6+5;
int n,k,a[N],st[N],ans=0,pos[N];
vi g[N];
ll seg[N*4],lz[N*4];
struct pq{
priority_queue<int> x,y;
void ins(int d){x.push(d);}
void del(int d){y.push(d);}
int tp(){
while(x.size()&&y.size()&&x.top()==y.top()){
x.pop();y.pop();
}
return x.top();
}
}q;
void pushdown(int p,int l,int r){
if(!lz[p]) return;
int mid=(l+r)>>1;
lz[ls(p)]=lz[p];
lz[rs(p)]=lz[p];
seg[ls(p)]=mid-l+1;
seg[rs(p)]=r-mid;
lz[p]=0;
}
void change(int p,int nx,int ny,int l,int r){
if(l<=nx&&ny<=r){
seg[p]=ny-nx+1;
lz[p]=1;
return;
}
pushdown(p,nx,ny);
int mid=(nx+ny)>>1;
if(l<=mid) change(ls(p),nx,mid,l,r);
if(mid<r) change(rs(p),mid+1,ny,l,r);
seg[p]=seg[ls(p)]+seg[rs(p)];
}
int query(int p,int nx,int ny,int l,int r){
if(l<=nx&&ny<=r){
return seg[p];
}
pushdown(p,nx,ny);
int mid=(nx+ny)>>1;
int sum=0;
if(l<=mid) sum+=query(ls(p),nx,mid,l,r);
if(mid<r) sum+=query(rs(p),mid+1,ny,l,r);
return sum;
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
g[a[i]].pb(i);
pos[i]=g[a[i]].size();
}
q.ins(0);
for(int r=1;r<=n-1;r++){
if(pos[r]!=1){
q.del(g[a[r]][pos[r]-2]);
}
if(pos[r]==g[a[r]].size()){
if(g[a[r]].size()>1) change(1,1,n,g[a[r]][0]+1,r);
int l=q.tp()+1;
int res=query(1,1,n,l,r);
ans+=r-l+1-res;
}else{
q.ins(r);
}
}
printf("%lld\n",ans);
return 0;
}