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

题目大意

题目链接

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

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

数据范围:\(1\leq n\leq 16\), \(1\leq q\leq 2\times10^5\)

本题题解

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

修改和询问,方法是类似的。这里我们以修改为例。暴力的做法是枚举所有\(t\in[0,2^n)\),判断是否满足\(x\subseteq t\subseteq y\)。容易想到一种更优雅的暴力:设\(z=y\setminus 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_{x\subseteq y}a_x\)。记询问\((x,y)\)对应的答案为\(f(x,y)\)。显然,当\(x=0\)时,\(f(0,y)=s_y\)

考虑\(x\neq 0\)时,取出任意一个\(x\)里是\(1\)的二进制位,例如,取出\(p=\operatorname{lowbit}(x)\)。因为\(x\subseteq 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+d\leq n\)。因此:\(\min(c,d)\leq\frac{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_1\operatorname{or} x_2,y_1\operatorname{and}y_2)\)。这个范围里的数的数量,就是\(2^{\text{bitcnt}((y_1\operatorname{and}y_2)\setminus(x_1\operatorname{or} x_2))}\)
  • 【前面块的修改】的贡献:可以把前面块修改后的高维前缀和数组\(s\)预处理好。然后根据\(c\), \(d\)的大小,枚举子集或递归查询即可(这和静态问题是一样的。所以说分块的好处就是,不同的块之间,相当于是静态问题)。

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

时间复杂度\(O(2^nn\cdot \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;
}
posted @ 2020-07-17 00:35  duyiblue  阅读(278)  评论(0编辑  收藏  举报