• 题解 洛谷P6562 [SBCOI2020] 归家之路


    题目大意

    题目链接

    给定(n)以及(a_0,a_1,dots,a_{2^n-1})的初始值。对于整数(x,y),定义(xsubseteq y)为真当且仅当(xoperatorname{and}y=x)(q)次操作,操作分两种:

    • 修改操作:(1 x y k),对于所有(xsubseteq tsubseteq y),把(a_t)加上(k)
    • 询问操作:(2 x y),对于所有(xsubseteq tsubseteq y),求出(a_t)的和。对(2^{32})取模。

    数据范围:(1leq nleq 16), (1leq qleq 2 imes10^5)

    本题题解

    对于所有修改和询问,我们首先判断它是否满足(xsubseteq y)。如果不满足,则可以直接忽略(如果是询问则答案等于(0))。以下只考虑(xsubseteq y)的情况。

    修改和询问,方法是类似的。这里我们以修改为例。暴力的做法是枚举所有(tin[0,2^n)),判断是否满足(xsubseteq tsubseteq y)。容易想到一种更优雅的暴力:设(z=ysetminus x),我们枚举(t'subseteq z),则所有合法的(t),都可以表示为(t'cup x)。我们很容易实现不重不漏地枚举子集。那么,如果设(y)(x)多的二进制位有(d)个,我们现在就可以(O(2^d))实现一次修改或查询。

    //不重不漏地枚举子集
    int z=x^y;
    for(int i=z;;i=(i-1)&z){
    	// do something ...
    	if(i==0)break;
    }
    

    不过显而易见的是,(d)可能达到(O(n))级别,所以仅有上述的暴力还是不够的。

    对于这种子集询问,可以将其看做(n)个维度,于是想到做高维前缀和。设(a)的高维前缀和数列为(s),即(s_{y}=sum_{xsubseteq y}a_x)。记询问((x,y))对应的答案为(f(x,y))。显然,当(x=0)时,(f(0,y)=s_y)

    考虑(x eq 0)时,取出任意一个(x)里是(1)的二进制位,例如,取出(p=operatorname{lowbit}(x))。因为(xsubseteq y),所以(y)的这一位也必为(1)。那么:

    [f(x,y)=f(x-p,y)-f(x-p,y-p) ]

    这很好理解:(f(x-p,y))的意思是,(p)这一位既可以选也可以不选。但是在符合(f(x,y))要求的数里,(p)这一位必须为(1),所以减去(p)这一位为(0)的情况(f(x-p,y-p))即可。

    我们可以按这个式子递归下去,每次(x)一定会减少一个二进制位,递归的边界就是(x=0)。设(x)里有(c)(1),则递归的复杂度是(O(2^c))的。

    这个复杂度看上去依然高达(O(2^n)),不过我们可以和上一个暴力结合一下。回顾:(d)(y)(x)多的二进制位数,那么(c+d)就是(y)的二进制位数,显然(c+dleq n)。因此:(min(c,d)leqfrac{n}{2})。所以结合这两种做法,单次修改或查询的复杂度可以降至(O(2^{frac{n}{2}}))

    不过别高兴得太早。这种做法,需要预先知道高维前缀和数组(s)。这在静态的问题(subtask3)中是好办的:修改时,维护一个标记数组(m)(m_y)表示对(y)所有子集都要加上这个数。所有修改完成后,做一遍高维后缀和(fwt and),就能还原出每个位置(a_i)的增量。然后对新的(a),做一遍高维前缀和(fwt or),就得到(s)了。两次fwt的复杂度都是(O(2^nn))


    但是在非静态的问题里,就不能这样处理(s)

    我们考虑对操作分块,每(B)个操作分为一块。

    对于修改操作,还是和上面一样进行,仍然维护一个标记数组(m),不过这个(m)只记录当前块的信息,跳到下一个块时要清空。

    对于查询操作,分别考虑【本块内的修改】和【前面块的修改】对答案的贡献。

    • 【本块内的修改】的贡献:枚举所有本块内的修改,设修改的范围是((x_1,y_1)),当前询问的范围是((x_2,y_2))。则这个修改对本次询问,产生贡献的范围就是((x_1operatorname{or} x_2,y_1operatorname{and}y_2))。这个范围里的数的数量,就是(2^{ ext{bitcnt}((y_1operatorname{and}y_2)setminus(x_1operatorname{or} x_2))})
    • 【前面块的修改】的贡献:可以把前面块修改后的高维前缀和数组(s)预处理好。然后根据(c), (d)的大小,枚举子集或递归查询即可(这和静态问题是一样的。所以说分块的好处就是,不同的块之间,相当于是静态问题)。

    每处理完一个块,就更新高维前缀和数组(s)

    时间复杂度(O(2^nncdot frac{q}{B}+(B+2^{frac{n}{2}})q))(B)(2000)左右可以AC。

    参考代码:

    //problem:P6562
    #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=16;
    const int MAXM=1<<MAXN;
    const int MAXQ=2e5;
    const int B=2000;
    //const int BCNT=MAXQ/B+10;
    
    int n,q,m,half_n;
    int bitcnt[MAXM+5];
    uint v[MAXM+5],s[MAXM+5],mdf_v[MAXM+5],mdf_s[MAXM+5];
    
    template<typename T>
    void fwt_or(T* a,int n,T flag){
    	for(int i=1;i<n;i<<=1){
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0;k<i;++k){
    				a[j+k+i]+=a[j+k]*flag;
    			}
    		}
    	}
    }
    template<typename T>
    void fwt_and(T* a,int n,T flag){
    	for(int i=1;i<n;i<<=1){
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0;k<i;++k){
    				a[j+k]+=a[i+j+k]*flag;
    			}
    		}
    	}
    }
    
    struct ModifyInfo{
    	int a,b;uint k;
    	ModifyInfo(){}
    	ModifyInfo(int _a,int _b,uint _k){
    		a=_a;b=_b;k=_k;
    	}
    }sta[B+5];
    int top;
    
    void modify_work(int a,int b,uint k){
    	if(!a){
    		mdf_s[b]+=k;
    		return;
    	}
    	int lb=(a&(-a));
    	modify_work(a-lb,b,k);
    	modify_work(a-lb,b-lb,-k);
    }
    void modify(int a,int b,uint k){
    	if((a&b)!=a)return;
    	sta[++top]=ModifyInfo(a,b,k);
    	if(bitcnt[a^b] <= half_n){
    		int dif=a^b;
    		for(int i=dif;;i=(i-1)&dif){
    			mdf_v[a^i]+=k;
    			if(i==0)break;
    		}
    		return;
    	}
    	modify_work(a,b,k);
    }
    uint query_work(int a,int b){
    	if(!a){
    		return s[b];
    	}
    	int lb=(a&(-a));
    	return query_work(a-lb,b)-query_work(a-lb,b-lb);
    }
    uint query(int a,int b){
    	if((a&b)!=a)return 0;
    	uint res=0;
    	// 本块内的修改对本次询问的贡献
    	for(int i=1;i<=top;++i){
    		int na=(a|sta[i].a);
    		int nb=(b&sta[i].b);
    		if((na&nb)!=na)continue;
    		res+=(1u<<bitcnt[na^nb])*sta[i].k;
    	}
    	if(bitcnt[a^b] <= half_n){
    		int dif=a^b;
    		for(int i=dif;;i=(i-1)&dif){
    			res+=v[a^i];
    			if(i==0)break;
    		}
    		return res;
    	}
    	//之前的块的贡献
    	res+=query_work(a,b);
    	return res;
    }
    void rebuild(){
    	fwt_and(mdf_s,m,1u);//懒标记下放
    	for(int i=0;i<m;++i){
    		v[i] += mdf_v[i] + mdf_s[i];
    		s[i]=v[i];
    		mdf_v[i]=mdf_s[i]=0;
    	}
    	fwt_or(s,m,1u);
    	top=0;
    }
    int main() {
    	cin>>n>>q;
    	m=1<<n;
    	half_n=n>>1;
    	for(int i=0;i<m;++i){
    		cin>>v[i];
    		s[i]=v[i];
    	}
    	fwt_or(s,m,1u);//高维前缀和
    	
    	for(int i=1;i<m;++i)
    		bitcnt[i]=bitcnt[i>>1]+(i&1);//预处理bitcnt
    	
    	for(int tq=1;tq<=q;++tq){
    		int op;cin>>op;
    		if(op==1){
    			int a,b;uint k;
    			cin>>a>>b>>k;
    			modify(a,b,k);
    		}
    		else{
    			int a,b;
    			cin>>a>>b;
    			cout<<query(a,b)<<endl;
    		}
    		
    		if(tq%B==0)rebuild();
    	}
    	return 0;
    }
    
  • 相关阅读:
    终于有人把MYSQL索引讲清楚了
    计算机基础知识总结与操作系统 PDF 下载
    java电子书python电子书等PDF下载方式
    redis布隆过滤器
    IntelliJ IDEA 2020.1 激活教程,亲测可用
    Another Redis DeskTop Manager一款稳定全新的redis连接工具
    一文吃透redis持久化,妈妈再也不担心我面试过不了!
    ES6 运算符
    If-Else的5种方法从入门到高级示例
    ES6中 的类(class)
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13326768.html
Copyright © 2020-2023  润新知