题意简述
给一个长度为
n
n
n 的序列
a
a
a,支持
q
q
q 个操作:
I l r x 区间元素整体
+
x
+x
+x。
R l r 区间元素整体
×
(
−
1
)
\times (-1)
×(−1)
Q l r x 询问:从
[
l
,
r
]
[l,r]
[l,r] 中选择
x
x
x 个数的积的所有方案的和,
m
o
d
19940417
\bmod 19940417
mod19940417。
n , q ≤ 5 × 1 0 4 n,q\le 5\times 10^4 n,q≤5×104。
所有操作保证
[
l
,
r
]
[l,r]
[l,r] 有意义。操作 I 中,
x
≤
1
0
9
x\le 10^9
x≤109;操作 Q 中,
x
≤
m
i
n
(
r
−
l
+
1
,
20
)
x\le min(r-l+1,20)
x≤min(r−l+1,20)
思路
我们观察到 x ≤ 20 x\le 20 x≤20。不妨在线段树中维护每一种情况的答案。设 S [ x ] S[x] S[x] 表示当前节点中选择 x x x 个数的积的所有方案的和。为了方便考虑,设 S [ 0 ] = 1 S[0]=1 S[0]=1。然后,显然还需要维护取相反数的标记和加法标记。
然后开始线段树传 统 艺 能了。
如何合并左右儿子的答案
我们在左半边选 a a a 个,右半边选 b b b 个,那就一共选了 a + b a+b a+b 个,贡献是 l S [ a ] × r S [ b ] lS[a]\times rS[b] lS[a]×rS[b]。其中 l S , r S lS,rS lS,rS 表示左儿子,右儿子的 S S S 数组。
如何单节点加值
我们其实就是要考虑这样一个东西:
a 1 × a 2 ⋯ × a m a_1\times a_2\cdots \times a_m a1×a2⋯×am 如何变成 ( a 1 + x ) ( a 2 + x ) ⋯ ( a m + x ) (a_1+x)(a_2+x)\cdots (a_m+x) (a1+x)(a2+x)⋯(am+x)
每个括号里可能是 a i a_i ai,也可能是 x x x。
枚举 i i i 个括号出了 a i a_i ai,剩下 m − i m-i m−i 个括号出了 x x x,贡献为:
(a 中选 i 个数的积的所有方案的和)
×
x
m
−
i
\times x^{m-i}
×xm−i。
那么,
S
[
m
]
S[m]
S[m] (不要想歪)该怎么算呢?还是不好算。
其实是因为还差一步转换:我们考虑每个 i i i 给 m m m 的贡献。
根据上面的式子,当前的 i i i 会给 S [ m ] S[m] S[m] 加上 S [ i ] × x m − i S[i]\times x^{m-i} S[i]×xm−i 。那么,加了多少遍呢?我们上面假设现在钦定了 i i i 个括号出了 a i a_i ai,然后剩下 m − i m-i m−i 个括号出了 x x x。剩下 m − i m-i m−i 个括号显然不是唯一确定的,那么它们有多少种选择呢?显然是 C l e n − i m − i C_{len-i}^{m-i} Clen−im−i。其中 l e n len len 表示线段树当前区间的长度。
于是,
S
[
m
]
=
∑
i
=
1
m
S
[
i
]
×
x
m
−
i
×
C
l
e
n
−
i
m
−
i
S[m]=\sum\limits_{i=1}^{m} S[i]\times x^{m-i}\times C_{len-i}^{m-i}
S[m]=i=1∑mS[i]×xm−i×Clen−im−i
如何单节点取反
显然,取反只会改变正负性,不改变绝对值,而且只有奇数位置会被改变正负性。
即:对于奇数的 i i i, S [ i ] S[i] S[i] 变为 − S [ i ] -S[i] −S[i]。
到此,这题涉及到的全部操作都解决了。
代码
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 54444
#define mod 19940417
#define int long long
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define iter(a,p) (a.begin()+p)
int I()
{
int x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return (x=(f==1)?x:-x);
}
void Rd(int cnt,...)
{
va_list args; va_start(args,cnt);
F(i,1,cnt) {int* x=va_arg(args,int*);(*x)=I();}
va_end(args);
}
// ==================== 预处理组合数
int CC[N][30];
void Init()
{
CC[0][0]=1;
int n=5e4;
F(i,1,n)
{
CC[i][0]=1;
F(j,1,min(20ll,i)) CC[i][j]=(CC[i-1][j-1]+CC[i-1][j])%mod;
}
}
// ====================
struct node{int l,r; bool f; int a,ans[22];};
node operator+(node ls,node rs) // 合并区间就用重载运算符了
{
node cur;
cur.l=ls.l; cur.r=rs.r; cur.f=0; cur.a=0;
FK(cur.ans);
F(i,0,min(20ll,ls.r-ls.l+1)) F(j,0,min(20ll-i,rs.r-rs.l+1))
{
cur.ans[i+j]=(cur.ans[i+j]%mod+ls.ans[i]*rs.ans[j]%mod+2*mod)%mod;
// cur.ans[i+j]+=ls.ans[i]*rs.ans[j]
// 话说取膜好丑啊...
}
return cur;
}
class SegmentTree
{
public:
node tree[N<<2];
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define C tree[index].f
#define A tree[index].a
#define S tree[index].ans
#define lL tree[ls].l
#define lR tree[ls].r
#define lC tree[ls].f
#define lA tree[ls].a
#define lS tree[ls].ans
#define rL tree[rs].l
#define rR tree[rs].r
#define rC tree[rs].f
#define rA tree[rs].a
#define rS tree[rs].ans
#define up(index) tree[index]=tree[ls]+tree[rs]
void Build(int l,int r,int index)
{
L=l,R=r; C=A=0; FK(S);
if (l==r)
{
S[0]=1; S[1]=(I()%mod+mod)%mod; // 为了方便合并左右,设 S[0]=1
return;
}
int mid=(l+r)>>1;
Build(l,mid,ls); Build(mid+1,r,rs);
up(index);
}
void AddOne(int x,int index)
{
A=(A+x)%mod;
int p[22]; p[0]=1;
F(i,1,min(20ll,R-L+1)) p[i]=p[i-1]*x%mod; // 预处理出幂
S[0]=1;
D(i,min(20ll,R-L+1),1) F(j,0,i-1)
{
S[i]=(S[i]+S[j]*p[i-j]%mod*CC[R-L+1-j][i-j]%mod)%mod;
// S[i]+=S[j]*x^(i-j)*CC[len-j][i-j];
}
F(i,0,min(20ll,R-L+1)) S[i]=(S[i]%mod+mod)%mod;
}
void FlipOne(int index)
{
F(i,1,min(20ll,R-L+1)) S[i]=(i&1)?(mod-S[i]):S[i];
// 奇数位置取反
A=mod-A;
// 注意:取反会影响到加法标记的!!!
C^=1;
}
void PushDown(int index)
{
if (C)
{
FlipOne(ls); FlipOne(rs); C=0;
}
if (A)
{
AddOne(A,ls); AddOne(A,rs); A=0;
}
}
void Add(int l,int r,int x,int index)
{
if (l>R or L>r) return;
if (l<=L and R<=r) return AddOne(x,index);
PushDown(index);
Add(l,r,x,ls); Add(l,r,x,rs);
up(index);
}
void Flip(int l,int r,int index)
{
if (l>R or L>r) return;
if (l<=L and R<=r) return FlipOne(index);
PushDown(index);
Flip(l,r,ls); Flip(l,r,rs);
up(index);
}
node Query(int l,int r,int index)
{
if (l<=L and R<=r) return tree[index];
PushDown(index);
int mid=(L+R)>>1;
if (mid<l) return Query(l,r,rs);
if (r<=mid) return Query(l,r,ls);
return Query(l,r,ls)+Query(l,r,rs);
}
}T;
int n,m;
void Input()
{
Rd(2,&n,&m);
T.Build(1,n,1);
}
void Soviet()
{
Init();
F(i,1,m)
{
char s[3]; scanf("%s",s);
if (s[0]=='I')
{
int l,r,x; Rd(3,&l,&r,&x);
T.Add(l,r,x,1);
}
if (s[0]=='R')
{
int l,r; Rd(2,&l,&r);
T.Flip(l,r,1);
}
if (s[0]=='Q')
{
int l,r,k; Rd(3,&l,&r,&k);
node ans=T.Query(l,r,1);
printf("%lld\n",ans.ans[k]);
}
}
}
#define Flan void
Flan IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}
博客详细介绍了如何处理一个长度为 n 的序列 a,在支持区间加法、乘法以及询问指定数量元素积的和的操作下,利用线段树进行动态维护的思路。通过维护每个节点中不同数量选择的元素积的和,并处理单节点加值和取反的情况,实现了高效解法。
415

被折叠的 条评论
为什么被折叠?



