acm-(区间覆盖)Educational Codeforces Round 95 (Rated for Div. 2) G. Three Occurrences

题面
传送门
本题考虑固定右端点 r r r,然后看有多少个左端点 l l l符合条件。
那么如何找合法的 l l l呢,我们假设数组 l a s t [ a [ r ] ] [ 0 ∼ 2 ] last[a[r]][0\sim 2] last[a[r]][02]分别表示元素 a [ r ] a[r] a[r] r r r之前(不包括 r r r)最近的第一个位置、第二个位置、第三个位置。

容易得出 l l l必定位于 l a s t [ a [ r ] ] [ 2 ] + 1 ∼ l a s t [ a [ r ] ] [ 1 ] last[a[r]][2]+1\sim last[a[r]][1] last[a[r]][2]+1last[a[r]][1],由于 l a s t [ a [ r ] ] [ 1 ] + 1 ∼ r last[a[r]][1]+1\sim r last[a[r]][1]+1r区间范围内的其它数必然也会存在一个合法区间,我们要找的就是这些合法区间的交集。不妨考虑用线段树维护,对于 r r r而言我们给 l a s t [ a [ r ] ] [ 0 ] + 1 ∼ r last[a[r]][0]+1\sim r last[a[r]][0]+1r的区间打上 1 1 1的标记,给 l a s t [ a [ r ] ] [ 2 ] + 1 ∼ l a s t [ a [ r ] ] [ 1 ] last[a[r]][2]+1\sim last[a[r]][1] last[a[r]][2]+1last[a[r]][1]的区间打上 − 1 -1 1的标记,给 1 ∼ l a s t [ a [ r ] ] [ 2 ] 1\sim last[a[r]][2] 1last[a[r]][2]的区间打上1的标记。我们要统计的就是权值为0的位置的个数。

为什么这样做是正确的的呢?可以这样想,对于一个位置而言,它会对合法区间作出0的总贡献,对于其它位置而言也是如此,因此所有合法区间的交集必定是权值为0的位置。用线段树维护的话我们可以类似于扫描线一样只打覆盖标记即可,不需要 p u s h d o w n pushdown pushdown,这样做得原因在于对于同一个元素而言,它对区间作出的贡献总是一段一段固定的,比如某个区间第一次被 + 1 +1 +1以后,下一次(如果后面还有这个元素)一定会被 − 1 -1 1,并且只有当这个区间被 − 1 -1 1以后这个区间才有机会成为合法区间,所以不必担心 p u s h d o w n pushdown pushdown的问题。

int n,last[maxn][3],t[maxn<<2],lz[maxn<<2];
void pushup(int rt,int l,int r){
	if(lz[rt])t[rt]=0;
	else if(l==r)t[rt]=1;
	else t[rt]=t[rt<<1]+t[rt<<1|1];
}
void build(int rt,int l,int r){
	if(l==r){
		t[rt]=1;
		return;
	}
	int mid=l+r>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	pushup(rt,l,r);
}
int qry(int rt,int l,int r,int x){
	if(l>x)return 0;
	if(r<=x)return t[rt];
	int mid=l+r>>1;
	return qry(rt<<1,l,mid,x)+qry(rt<<1|1,mid+1,r,x);
}
void cg(int rt,int l,int r,int ql,int qr,int val){
	if(ql<=l && r<=qr){
		lz[rt]+=val;
		pushup(rt,l,r);
		return;
	}
	int mid=l+r>>1;
	if(ql<=mid && qr>=l)cg(rt<<1,l,mid,ql,qr,val);
	if(ql<=r && qr>=mid+1)cg(rt<<1|1,mid+1,r,ql,qr,val);
	pushup(rt,l,r);
}

int main(){
	n=rd();
	build(1,1,n);
	ll ans=0;
	memset(last,-1,sizeof(last));
	FOR(i,1,n+1){
		int u=rd();
		if(last[u][0]==-1){
			cg(1,1,n,1,i,1);
		}else{
			cg(1,1,n,last[u][0]+1,i,1);
			if(last[u][1]!=-1){
				if(last[u][2]==-1)cg(1,1,n,1,last[u][1],-1);
				else {
					cg(1,1,n,last[u][2]+1,last[u][1],-1);
					cg(1,1,n,1,last[u][2],1);
				}
			}
		}
		ans+=qry(1,1,n,i);
		last[u][2]=last[u][1];
		last[u][1]=last[u][0];last[u][0]=i;
	}
	printf("%lld\n",ans);
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值