CF1004F Sonya and Bitwise OR(线段树平衡复杂度+or 前缀性质)

CF1004F Sonya and Bitwise OR

有一个长度为 \(n\) 的数组 \(\{a\}\),有 \(m\) 次操作,又给定一个数 \(x\),有两类操作:

  • 1 i y\(a_i\) 改为 \(y\)
  • 2 l r 查询有多少个区间 \([L,R]\) 满足 \(a_{L\dots R}\) 的按位或 \(\tt{or}\) 大于等于 \(x\)

\(1\le n,m\le 10^5;0\le a_i,x,y\le 2^{20}\)

先考虑如果只有询问操作怎么做。

那可以算出这段区间的中点开始,算出 \([l,m]\) 的后缀 \(\tt{or}\)\([m+1,r]\) 的前缀 \(\tt{or}\)

那么可以通过双指针来求出每一个符合条件的区间,如果改为多次询问就用树状数组记下来,一次 \(\log n\) 查询,总复杂度 \(n\log n\)

\(\bigstar\texttt{Hint}\):到这一步之后就一直在想用 CDQ 分治来完成对询问的处理,但其实因为是单点修改,多次询问,可以思考用线段树维护这一过程。而且线段树自带分治功能。

\(\color{yellow}{\bigstar\texttt{Trick}}\):一个非常重要的性质,在一段数的 \(\tt{or}\) 前缀中,只可能出现 \(\log_2 a\) 个可能的值。每次最多让一个 \(0\) 位变成 \(1\),因此最多只会有 \(\log_2a\) 段。

那么可以在线段树中维护区间内的前后缀 \(\tt{or}\) 段,每个区间都不会超过 \(20\) 个,同时记录这个区间内的合法子区间数量,像线段树一样修改询问即可。

总结:

最开始的 CDQ 想法大体方向是正确的,和正解都利用了分治。但是存在修改操作后,对区间分治就不能够在 CDQ 上进行,必须另寻他路。

那么接下来就应当考虑在线解决这个问题,这道题的区间是可以合并的,而平衡复杂度的思想就让我们想到了线段树

最后这个经典的 \(\tt{Trick}\) 我不能够熟练使用,下次就知道了。

#define Maxn 100005
int n,m,X;
int a[Maxn];
struct SUB
{
	SUB(int Val=0,int Pl=0,int Pr=0):val(Val),pl(Pl),pr(Pr){}
	int val,pl,pr;
};
SUB tpre[100],tsuf[100];
struct TREE
{
	ll sum;
	vector<SUB> pre,suf;
	TREE(){ sum=0,pre.clear(),suf.clear(); }
	inline void push(int x,int Nu)
	{
		pre.clear(),suf.clear(),sum=(x>=X),
		pre.pb(SUB(x,Nu,Nu)),suf.pb(SUB(x,Nu,Nu));
	}
}tree[Maxn<<2];
inline TREE merge(TREE L,TREE R)
{
	int ppre=0,psuf=0,tl=L.pre.back().val,tr=R.suf.front().val;
	for(SUB v:L.pre) tpre[++ppre]=v;
	for(SUB v:R.pre) tpre[++ppre]=SUB(v.val|tl,v.pl,v.pr);
	for(SUB v:L.suf) tsuf[++psuf]=SUB(v.val|tr,v.pl,v.pr);
	for(SUB v:R.suf) tsuf[++psuf]=v;
	tpre[ppre+1]=SUB(-1,0,0),tsuf[psuf+1]=SUB(-1,0,0);
	TREE ret; ret.sum=L.sum+R.sum;
	for(int nl=0,nr=0,sl=L.suf.size()-1,sr=R.pre.size()-1;nl<=sl;nl++)
	{
		while(nr<=sr && (L.suf[nl].val|R.pre[nr].val)<X) nr++;
		if(nr>sr) break;
		ret.sum+=1ll*(R.pre[sr].pr-R.pre[nr].pl+1)*
			(L.suf[nl].pr-L.suf[nl].pl+1);
	}
	for(int i=2,Last=0;i<=ppre+1;i++) if(tpre[i].val!=tpre[i-1].val)
		assert(tpre[Last+1].val==tpre[i-1].val),
		ret.pre.pb(SUB(tpre[i-1].val,tpre[Last+1].pl,tpre[i-1].pr)),Last=i-1;
	for(int i=2,Last=0;i<=psuf+1;i++) if(tsuf[i].val!=tsuf[i-1].val)
		assert(tsuf[Last+1].val==tsuf[i-1].val),
		ret.suf.pb(SUB(tsuf[i-1].val,tsuf[Last+1].pl,tsuf[i-1].pr)),Last=i-1;
	assert(ret.pre.size()<=30 && ret.suf.size()<=30);
	return ret;
}
void build(int p,int nl,int nr)
{
	if(nl==nr) { tree[p].push(a[nl],nl); return; }
	int mid=(nl+nr)>>1;
	build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
	tree[p]=merge(tree[p<<1],tree[p<<1|1]);
}
void change(int p,int nl,int nr,int x,int k)
{
	if(nl==nr) { tree[p].push(k,x); return; }
	int mid=(nl+nr)>>1;
	if(mid>=x) change(p<<1,nl,mid,x,k);
	else change(p<<1|1,mid+1,nr,x,k);
	tree[p]=merge(tree[p<<1],tree[p<<1|1]);
}
TREE query(int p,int nl,int nr,int l,int r)
{
	if(nl>=l && nr<=r) return tree[p];
	int mid=(nl+nr)>>1;
	if(mid>=l && mid<r)
		return merge(query(p<<1,nl,mid,l,r),query(p<<1|1,mid+1,nr,l,r));
	else if(mid>=l) return query(p<<1,nl,mid,l,r);
	else return query(p<<1|1,mid+1,nr,l,r);
}
int main()
{
	n=rd(),m=rd(),X=rd();
	for(int i=1;i<=n;i++) a[i]=rd();
	build(1,1,n);
	for(int i=1,opt,x,y;i<=m;i++)
	{
		opt=rd(),x=rd(),y=rd();
		if(opt==1) change(1,1,n,x,y);
		else printf("%lld\n",query(1,1,n,x,y).sum);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值