题解 洛谷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;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13326768.html