*题解:P10045 [CCPC 2023 北京市赛] 线段树

题目链接

解析

根据题意,\(a_i\) 在任意时刻都是奇数。也就是说 \(b_i=a_i - 1\) 在任意时刻都是偶数。对于 \(\prod_{i \in [l,r]}(b_i + 1)\),由于模数是 \(2^{20}\),且该式展开后任意含有大于等于 \(20\)\(b_i\) 的项,都含有因子 \(2^{20}\),故它们取模后都会变成 \(0\)。故考虑在线段树上维护 \(f_i\) 表示含有 \(i\)\(b_i\) 的项的乘积,最终答案是 \(\sum_{i=0}^{19}f_i\)

考虑怎么合并 \(f\),直接枚举左儿子含有多少个 \(b\) 的项:

\[f_i=\sum_{j=0}^i f^{(L)}_{j}\cdot f^{(R)}_{i-j} \]

\(f^{(L)},f^{(R)}\) 分别为左右儿子的 \(f\)

考虑怎么修改,即求 \(x\)\(f\) 的贡献。考虑组合意义,每轮可以视为有 \(a_i\) 个本质不同的深色球和 \(x\) 个本质不同浅色球供选取,最终结果就是选取的方案数总和,这样修改前的 \(f_i\) 就表示选了 \(i\) 个深色球的方案数。考虑修改之后选了 \(k\) 个球的方案 \(g_k\),,枚举深色球数量 \(i\),那么浅色球数量就是 \(k - i\),有:

\[g_k = \sum_{i=0}^k f_i \cdot C_{len - i}^{k-i} \cdot x^{k-i} \]

其中 \(len\) 为当前区间长度,\(C_{len - i}^{k - i}\) 统计的是在总共的 \(len - i\) 次选择机会(选深色球已经消耗了 \(i\) 次机会)中消耗 \(k-i\) 次选择机会选浅色球。暴力计算即可。

考虑怎么求答案,假设已经知道左右儿子的答案(乘积),那么直接乘起来就好。

时间复杂度 \(O((q \log n)M^2)\)\(M\)\(20\)

卡常小技巧。由于模运算有性质:当 \(m∣n\) 时,\((a\bmod n)\bmod m=a\mod m\)。故 \((a \bmod 2^{30})\bmod 2^{20}=a\bmod 2^{20}\),故可以利用 unsigned 的自然溢出来减少取模次数。

代码

/*
*/
#include <bits/stdc++.h>
#define eps 0.0000000001
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
typedef long long ll;
typedef unsigned ui;
typedef pair<int,int> pii;
const int N = 2e5 + 5,M = 20,P = 2000000,mod = 1 << 20,mod2 = 1e9 + 7,b1 = 131,b2 = 13331;
ui a[N],f[N << 2][M],tag[N << 2],C[N][M],mi[M];
int n,q;
void push_up(int p){
	for(int i=0;i<M;i++){
		f[p][i] = 0;
		for(int j=0;j<=i;j++){
			f[p][i] += f[ls(p)][j] * f[rs(p)][i - j];
		}
	}
}
void build(int p,int l,int r){
	if(l == r){
		f[p][0] = 1;
		f[p][1] = a[l];
		return;
	}
	build(ls(p),l,mid),build(rs(p),mid + 1,r);
	push_up(p);
}
void add_tag(int p,int l,int r,int x){
	int len = r - l + 1;
	tag[p] += x;
	for(int i=1;i<M;i++){
		mi[i] = mi[i - 1] * x;
	}
	ui tmp[M];
	memcpy(tmp,f[p],sizeof(tmp));
	for(int i=0;i<min(M,len + 1);i++){
		f[p][i] = 0;
		for(int j=0;j<=i;j++){
			f[p][i] += tmp[j] * C[len - j][i - j] * mi[i - j]; 
		}
	}
}
void push_down(int p,int l,int r){
	if(!tag[p]) return;
	add_tag(ls(p),l,mid,tag[p]);
	add_tag(rs(p),mid + 1,r,tag[p]);
	tag[p] = 0;
}
void add(int p,int l,int r,int L,int R,int x){
	if(l > R || r < L) return;
	if(l >= L && r <= R){
		add_tag(p,l,r,x);
		return;
	}
	push_down(p,l,r);
	add(ls(p),l,mid,L,R,x),add(rs(p),mid + 1,r,L,R,x);
	push_up(p);
} 
ui ask(int p,int l,int r,int L,int R){
	if(l > R || r < L) return 1; 
	if(l >= L && r <= R){
		ui res = 0;
		for(int i=0;i<M;i++){
			res += f[p][i];
		}
		return res;
	}
	push_down(p,l,r);
	return ask(ls(p),l,mid,L,R) * ask(rs(p),mid + 1,r,L,R);
	
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	mi[0] = 1;
	
	cin>>n>>q;
	for(int i=0;i<=n;i++){
		C[i][0] = 1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<M;j++){
			C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
		}
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i]--;
	}
	build(1,1,n);
	while(q--){
		int op,l,r,x;
		cin>>op>>l>>r;
		if(op == 1){
			cin>>x;
			add(1,1,n,l,r,x);
		}else{
			ui res = ask(1,1,n,l,r);
			cout<<res % mod<<"\n";
		}
	}
	return 0;
}
posted @ 2026-06-11 23:54  yutar  阅读(7)  评论(0)    收藏  举报