ABC346G

题意:

给定一个 N N N,求所有 N N N 的子区间 [ l , r ] , 1 ≤ l ≤ r ≤ N [l,r],1\leq l\leq r\leq N [l,r],1lrN 中满足 i ∈ [ l , r ] i \in [l,r] i[l,r] 中有至少一个 A i A_i Ai 的出现次数有且仅出现一次。

题意很明确,如何解决?
暴力:

直接 N 2 N^2 N2 扫一遍然后进行每个区间的特判即可,复杂度 O ( N 3 ) O(N^3) O(N3) 估计只能过样例。

莫队:

由于发现对于一个区间 [ l , r ] [l,r] [l,r] 只要其中一个 i ∈ [ l , r ] i \in [l,r] i[l,r] 中的 c o u n t ( A i ) count(A_i) count(Ai) 仅仅出现一次且拓展的 [ l , r ] [l,r] [l,r] 如果不等于 A i A_i Ai 那么这个拓展的区间也是合法的区间。故可以用莫队做,但是可能会很麻烦。具体的:

inline void upd(int x){
  mp[a[x]]--;
}
inline bool que(int x){
  if(mp[a[x]]>=1){
    return false;
  }
  return true;
}

这样做是 O ( N 2 N ) O(N^2\sqrt N) O(N2N ) 的反正也过不了。

正解:

考虑用 L i L_i Li 表示 i i i 最左出现的位置, R i R_i Ri 表示 i i i 最右边出现的位置,那么对于一个 i i i 肯定满足 [ L A i , i ] [L_{A_i},i] [LAi,i] 满足和 [ i , R A i ] [i,R_{A_i}] [i,RAi] 满足,并且 [ L A i − 1 , . . . . . k < R ] [L_{A_i}-1,.....k<R] [LAi1,.....k<R] 都满足,同理右端点也一样,所以由乘法原理得所有满足的区间个数有 ( R i − i + 1 ) ( i − L i + 1 ) (R_i-i+1)(i-L_i+1) (Rii+1)(iLi+1) 个。
但是这样做会发现有很多数会重复计算,所有可以用扫描线算法每个 i i i 对应了左上角为 ( L i , R i ) (L_i,R_i) (Li,Ri) 右下角为 ( i , i ) (i,i) (i,i) 的矩形来计算重合的部分,(只不过是一维的)也可以用二维线段树的区间赋值,于是答案即为重合部分的面积,复杂度为 O ( N log ⁡ 2 N ) O(N\log_2 N) O(Nlog2N) 可以过的。

具体的:

p o s i , j , k pos_{i,j,k} posi,j,k 表示 i i i j j j 后面(也就是右面) 所出现的第 k k k 次的位置。
那么有形如双指针的方法枚举左端点 l l l 考虑 r r r 的取值,即-右端点 r r r 应该满足的条件,显然,当有一个数 x x x 时,左端点为 l l l 则右端点为 [ p o s x , l , 1 , p o s x , l , 2 ) [pos_{x,l,1},pos_{x,l,2}) [posx,l,1,posx,l,2) 这里 p o s x , l , 2 pos_{x,l,2} posx,l,2 不能取,因为如果取就会出现两个 x x x
我们要在每次 l l l 往右都有且仅有一个值给 l l l 这个值就是 A l A_l Al 那么会给 l l l 所贡献的区间会从 [ l , p o s l , a l , 2 ) [l,pos_{l,a_l,2}) [l,posl,al,2) 同理,往后的区间会是 [ p o s l , a l , 2 , p o s l , a l , 3 ) [pos_{l,a_l,2},pos_{l,a_l,3}) [posl,al,2,posl,al,3) 其实就是把这个区间向后移了一位
然后发现这个并集大小的维护实际上是要我们支持区间加 / 减查询区间非 0 数个数,同时保证任意时刻所有数非负。于是直接把扫描线维护矩形面积并的线段树拿过来即可。具体地,维护区间内最小值及其个数,查询时若区间最小值为 0,则返回区间长度减去最小值个数,否则返回区间长度。

代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,a[N],l[N],r[N];
int mp[N];
long long ans=0;

struct edge{
	int ll,rr,hh,ff;
}e[N<<1];
int cnt;
inline int ls(int p){
	return p<<1;
}
inline int rs(int p){
	return p<<1|1;
}
void add(int l,int r,int h,int f){
	e[++cnt].ll=l;
	e[cnt].rr=r;
	e[cnt].hh=h;
	e[cnt].ff=f;
}
bool operator <(const edge &a,const edge &b){
	if(a.hh==b.hh)return a.ff>b.ff;
	return a.hh<b.hh; 
}
struct node{
	int sum,len;
}tr[N<<2];
void pushup(int p,int l,int r){
	if(tr[p].sum) tr[p].len=r-l+1;
	else if(l==r) tr[p].len=0;
	else tr[p].len=tr[ls(p)].len+tr[rs(p)].len;
}
void update(int p,int l,int r,int L,int R,int val){
	if(L<=l&&r<=R){
		tr[p].sum+=val;
		pushup(p,l,r);
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid)update(ls(p),l,mid,L,R,val);
	if(R>mid) update(rs(p),mid+1,r,L,R,val);
	pushup(p,l,r);
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);cout.tie(nullptr); 
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++){
		l[i]=mp[a[i]]+1;
		mp[a[i]]=i;
	}
	for(int i=1;i<N;i++) mp[i]=0;
	for(int i=n;i>=1;i--){
		if(mp[a[i]])r[i]=mp[a[i]]-1;
		else r[i]=n;
		mp[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		add(i,r[i]+1,l[i],1);
		add(i,r[i]+1,i+1,-1);
	}
	sort(e+1,e+1+cnt);
	for(int i=1;i<cnt;i++){
		update(1,1,n+1,e[i].ll,e[i].rr-1,e[i].ff);
		ans+=1ll*tr[1].len*(e[i+1].hh-e[i].hh);
	}
	cout<<ans;
	return 0;
}
  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值