【洛谷4247】[清华集训2012] 序列操作(线段树)

点此看题面

  • 给定一个长度为(n)的序列,要求支持三种操作:
    • 给一段区间加上一个数。
    • 将一段区间的数变成它们的相反数。
    • 询问从这段区间选(k)个数相乘的所有积之和。
  • (n,qle5 imes10^4,kle20)

线段树基础思路

这种选若干数相乘的所有积显然不太好求,而(k)这么小就是在明示我们要直接对于所有的(k)维护答案。

所以一个基础思路就是对于每个节点维护好这个区间内的所有答案。

而区间之间答案的合并是非常简单的,只要枚举从左区间选择(i)个数,右区间选择(j)个数,合在一起就是(i+j)个数,类似于一个卷积的形式。

针对修改的处理

首先,容易发现区间取反其实就是让选奇数个数的答案变成其相反数,选偶数个数的答案不变。

那么核心问题就在于如何解决给一段区间加上一个数(v)的问题。

我们枚举(i,j),考虑(v^{i-j})对于(i)的答案的贡献,那么能与之配成乘积的所有积之和就应该是(f_j),而方案数就应该是(C_{L-j}^{i-j})(其中(L)为区间大小,就表示从剩余数中选出这(i-j)个数的方案数)。

于是这道题就做完了。

代码:(O(nlognk^2))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define K 20
#define X 19940417
using namespace std;
int n,a[N+5],C[N+5][K+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	#define D isdigit(oc=tc())
	int Ff,OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0,Ff=1;W(!D) Ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),D);x*=Ff;}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	I void readc(char& x) {W(isspace(x=tc()));}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
}using namespace FastIO;
struct Data
{
	int L,f[K+5];I Data() {L=0,memset(f,0,sizeof(f));}
	I int& operator [] (CI x) {return f[x];}I int operator [] (CI x) Con {return f[x];}
	I Data operator + (Con Data& o) Con {Data t;RI i,j;t.L=L+o.L;//合并区间
		for(i=0;i<=K;++i) for(j=0;j<=K-i;++j) t[i+j]=(1LL*f[i]*o[j]+t[i+j])%X;return t;}//枚举两边各选多少数
	I Data operator + (CI v) Con {Data t;RI i,j,p;for(t[0]=1,t.L=L,i=1;i<=min(L,K);++i)//给所有数加v
		 for(p=1,j=i;~j;--j,p=1LL*p*v%X) t[i]=(1LL*p*f[j]%X*C[L-j][i-j]+t[i])%X;return t;}//枚举考虑v^(i-j)对f[i]的贡献
	I friend Data operator ~ (Data x) {for(RI i=1;i<=K;i+=2) x[i]=(X-x[i])%X;return x;}//取反,只对于奇数位
};
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) (O[x].V=O[x<<1].V+O[x<<1|1].V)//合并区间
		#define PD(x) (O[x].P&&(OP(x<<1),OP(x<<1|1),O[x].P=0),O[x].F&&(T(x<<1,O[x].F),T(x<<1|1,O[x].F),O[x].F=0))//下推标记
		#define T(x,v) (O[x].V=O[x].V+v,O[x].F=(O[x].F+v)%X)//打上加法标记
		#define OP(x) (O[x].V=~O[x].V,O[x].F=(X-O[x].F)%X,O[x].P^=1)//打上取反标记,注意把加法标记也取反
		struct node {Data V;int P,F;}O[N<<2];
	public:
		I void Build(PT)//建树
		{
			if(l==r) return (void)(O[rt].V[0]=1,O[rt].V[1]=a[l],O[rt].V.L=1);
			RI mid=l+r>>1;Build(LT),Build(RT),PU(rt);
		}
		I void A(CI L,CI R,CI v,PT)//区间加法
		{
			if(L<=l&&r<=R) return (void)T(rt,v);PD(rt);
			RI mid=l+r>>1;L<=mid&&(A(L,R,v,LT),0),R>mid&&(A(L,R,v,RT),0),PU(rt);
		}
		I void U(CI L,CI R,PT)//区间取反
		{
			if(L<=l&&r<=R) return (void)OP(rt);PD(rt);
			RI mid=l+r>>1;L<=mid&&(U(L,R,LT),0),R>mid&&(U(L,R,RT),0),PU(rt);
		}
		I Data Q(CI L,CI R,PT)//区间询问
		{
			if(L==l&&r==R) return O[rt].V;PD(rt);RI mid=l+r>>1;
			if(R<=mid) return Q(L,R,LT);if(L>mid) return Q(L,R,RT);return Q(L,mid,LT)+Q(mid+1,R,RT);
		}
}S;
int main()
{
	RI i,j,Qt;for(read(n,Qt),i=1;i<=n;++i) read(a[i]),a[i]<0&&(a[i]+=X);
	for(C[0][0]=i=1;i<=n;++i) for(C[i][0]=j=1;j<=min(i,K);++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%X;//预处理组合数
	char op;RI l,r,x;S.Build();W(Qt--) switch(readc(op),read(l,r),op)
	{
		case 'I':read(x),x<0&&(x+=X),S.A(l,r,x);break;case 'R':S.U(l,r);break;
		case 'Q':read(x),writeln(S.Q(l,r)[x]);break;
	}return clear(),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4247.html