码迷,mamicode.com
首页 > 其他好文 > 详细

题解 CF1004F Sonya and Bitwise OR

时间:2020-07-08 23:21:12      阅读:78      评论:0      收藏:0      [点我收藏+]

标签:支持   复杂度   合并   i+1   需要   href   预处理   前缀和   ons   

题目大意

题目链接

给定一个长度为\(n\)的序列\(a_1,a_2,\dots,a_n\)和一个非负整数\(x\)。你需要支持\(m\)次操作。操作有两种:

  • \(1\ i\ y\):把序列的第\(i\)个元素设为\(y\),也就是\(a_i:=y\)
  • \(2\ l\ r\):求有多少对\((L,R)\)满足\(l\leq L\leq R\leq r\)\(a_l,\dots a_r\)按位的和大于等于\(x\)

数据范围:\(1\leq n,m\leq 10^5,0\leq x,a_i,y<2^{20}\)\(1\leq i\leq n\)\(1\leq l\leq r\leq n\)

本题题解

考虑只有一次询问时怎么做。

分治。每次考虑\(L\)位于左半边,\(R\)位于右半边的情况(也就是“跨过中点”的答案)。再分别递归左、右两边。计算跨过中点的答案时,可以先求出【左半边的\(\operatorname{or}\)值后缀和】和【右半边的\(\operatorname{or}\)值前缀和】。然后用two pointers求出满足条件的\((L,R)\)数量,例如,可以枚举\(R\),则左半边的\(L\)可选范围单调增加,也就是从最左边不断向右移。这样,不算递归,每次做two pointers的复杂度都是\(O(\text{len})\)的,一次询问总复杂度就是\(O(\text{len}\log \text{len})\)

现在要支持单点修改多次查询。考虑用线段树来维护。

分治时的左、右两边,天然就是线段树的左、右节点,这有很多相似之处。但是我们面临的问题是:如果向分治时一样,维护出每个区间的前缀、后缀\(\operatorname{or}\)和,则一次修改、查询的复杂度,都高达\(O(n\log n)\),无法承受。

此时要用到最关键的一个性质:前缀、后缀的\(\operatorname{or}\)和,都只会分成\(O(\log a)\)个段,满足每段内值相同,不同段值不同。这是因为,前缀、后缀\(\operatorname{or}\)和,都是单调不下降的,每次增长,都至少多出一个为\(1\)的二进制位,所以最多增长\(\log_2 a\)次,也就是只有\(O(\log a)\)个段。

考虑对线段上每个区间,用两个\(\texttt{vector}\),分别维护该区间前缀、后缀\(\operatorname{or}\)和的这\(O(\log a)\)个段。同时,维护每个区间的答案(这个区间里有多少对\((L,R)\)满足......)。合并两个区间(也就是线段树\(\texttt{push_up}\)操作)时,先继承左、右儿子内部的答案,再用two pointers的方法求出跨过中点的答案(这和分治时是一样的)。于是我们就能\(O(\log a)\)实现\(\texttt{push_up}\)操作。也就能\(O(n\log a)\)建树(预处理)、\(O(\log n\log a)\)实现一次单点修改了。

查询时,线段树当前节点的区间,如果被查询的区间完全覆盖,直接返回维护好的答案;否则递归左、右儿子,再计算跨过中点的答案。所以单次查询也是\(O(\log n\log a)\)的。

总时间复杂度\(O(n\log a+m\log n\log a)\)

参考代码:

//problem:CF1004F
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=1e5;
int n,m,X,a[MAXN+5];
struct SegmentTree{
	int tl[MAXN*4+5],tr[MAXN*4+5];
	ll ans[MAXN*4+5];
	vector<pii>pre[MAXN*4+5],suf[MAXN*4+5];//(pos,val)
	
	void ck(int p){
		assert(SZ(pre[p]) && pre[p][0].fi==tl[p]);
		assert(SZ(suf[p]) && suf[p][0].fi==tr[p]);
	}
	void push_up(int p){
		int ls=p<<1,rs=p<<1|1;ck(ls);ck(rs);
		//1. ans
		ans[p]=ans[ls]+ans[rs];
		for(int i=0,j=SZ(suf[ls]);i<SZ(pre[rs]);++i){
			int nxt=(i<=SZ(pre[rs])-2?pre[rs][i+1].fi:tr[p]+1);
			//R \in [pre[rs][i].fi,nxt)
			while(j>0 && (suf[ls][j-1].se|pre[rs][i].se)>=X)
				--j;
			if(j!=SZ(suf[ls]))
				ans[p]+=(ll)(suf[ls][j].fi-tl[p]+1) * (nxt-pre[rs][i].fi);
		}
		
		//2. pre
		pre[p]=pre[ls];
		for(int i=0;i<SZ(pre[rs]);++i){
			if(pre[p].back().se != (pre[p].back().se | pre[rs][i].se)){
				pre[p].pb(mk(pre[rs][i].fi,pre[p].back().se|pre[rs][i].se));
			}
		}
		//3. suf
		suf[p]=suf[rs];
		for(int i=0;i<SZ(suf[ls]);++i){
			if(suf[p].back().se != (suf[p].back().se | suf[ls][i].se)){
				suf[p].pb(mk(suf[ls][i].fi,suf[p].back().se|suf[ls][i].se));
			}
		}
		
		assert(SZ(pre[p])<=21);
		assert(SZ(suf[p])<=21);
	}
	void build(int p,int l,int r){
		tl[p]=l;tr[p]=r;
		if(l==r){
			ans[p]=(a[l]>=X);
			pre[p].pb(mk(l,a[l]));
			suf[p].pb(mk(l,a[l]));
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		push_up(p);
	}
	void point_modify(int p,int l,int r,int pos,int v){
		if(l==r){
			ans[p]=(v>=X);
			pre[p][0]=mk(l,v);
			suf[p][0]=mk(l,v);
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)
			point_modify(p<<1,l,mid,pos,v);
		else
			point_modify(p<<1|1,mid+1,r,pos,v);
		push_up(p);
	}
	ll query(int p,int l,int r,int ql,int qr){
		assert(ql>=l);assert(qr<=r);
		if(ql==l && qr==r)
			return ans[p];
		int mid=(l+r)>>1;
		if(qr<=mid)
			return query(p<<1,l,mid,ql,qr);
		else if(ql>mid)
			return query(p<<1|1,mid+1,r,ql,qr);
		else{
			ll res=query(p<<1,l,mid,ql,mid) + query(p<<1|1,mid+1,r,mid+1,qr);
			int ls=p<<1,rs=p<<1|1;ck(ls);ck(rs);
			int j=SZ(suf[ls])-1;
			while(suf[ls][j].fi<ql)
				--j;
			assert(j>=0);
			for(int i=0;i<SZ(pre[rs]) && pre[rs][i].fi<=qr;++i){
				int nxt=(i<=SZ(pre[rs])-2?pre[rs][i+1].fi:tr[p]+1);
				ckmin(nxt,qr+1);
				if((suf[ls][j].se|pre[rs][i].se)<X)
					continue;
				while(j>0 && (suf[ls][j-1].se|pre[rs][i].se)>=X)
					--j;
				res+=(ll)(suf[ls][j].fi-ql+1) * (nxt-pre[rs][i].fi);
			}
			return res;
		}
	}
	SegmentTree(){}
}T;

int main() {
	cin>>n>>m>>X;
	for(int i=1;i<=n;++i)cin>>a[i];
	T.build(1,1,n);
	while(m--){
		int op;cin>>op;
		if(op==1){
			int i,y;cin>>i>>y;
			T.point_modify(1,1,n,i,y);
		}
		else{
			int l,r;cin>>l>>r;
			cout<<T.query(1,1,n,l,r)<<endl;
		}
	}
	return 0;
}

题解 CF1004F Sonya and Bitwise OR

标签:支持   复杂度   合并   i+1   需要   href   预处理   前缀和   ons   

原文地址:https://www.cnblogs.com/dysyn1314/p/13269575.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!