zoukankan      html  css  js  c++  java
  • 题解 CF1004F Sonya and Bitwise OR

    题目大意

    题目链接

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

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

    数据范围:(1leq n,mleq 10^5,0leq x,a_i,y<2^{20})(1leq ileq n)(1leq lleq rleq n)

    本题题解

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

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

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

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

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

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

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

    总时间复杂度(O(nlog a+mlog nlog 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;
    }
    
  • 相关阅读:
    关于不重启Tomcat自动加载改变的class文件
    Oracle数据库查询优化方案
    NavBarControl
    Spring Boot 热部署
    JSR303定义的校验类型
    C# JSON的序列化与反序列化
    常用正则表达式大全——包括校验数字、字符、一些特殊的需求等等
    什么是窗口句柄
    luffy前台配置
    luffy后台相关设置
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13269575.html
Copyright © 2011-2022 走看看