HDU 4578 Transformation 线段树+数学公式推导

题目链接

题目描述 Description

给出一个长度为N的序列,初始值全为0。支持三种操作,同时维护区间幂的和。
第一种操作:区间加上一个数。
第二种操作:区间乘上一个数。
第三种操作:区间变成一个数。

输入描述 Input Description

不会超过10组数据,每组数据给出一个N,M<100000。接下来M行描述操作和询问,三种操作分别对应(1,x,y,c),(2,x,y,c),(3,x,y,c),询问对应(4,x,y,p)表示询问从x到y的p次幂的和。(1 <= x <= y <= n, 1 <= c <= 10,000, 1 <= p <= 3) 一直读入到N=0,M=0为止。

输出描述 Output Description

对于每一次询问输出取模10007后的答案

水题分析 Waterproblem Analysis

首先很轻松的可以看出来这是一道线段树的题目,题目的要求的幂的和,符合线段树的区间可合并性。暂时确定这道题的使用线段树。

接下来看题目要求维护的幂的和,p<=3,我们可以同时维护这三个值然后分别对应的输出即可。但是幂的和与和的幂明显不同不能直接求得,要根据左右儿子的和相加得到。分别用 S 1 S_1 S1, S 2 S_2 S2, S 3 S_3 S3来表示该区间的幂的和。

依次来讨论三个操作。

只考虑当前修改的区间,对于操作一:
S 1 S_1 S1很好求得就是 S 1 S_1 S1+区间长度 L L L*加数 X X X即。

S 1 ′ = S 1 + L ∗ X S_1'=S_1+L*X S1=S1+LX

求S2时先看区间只有一个数时得情况,设未知数 A A A

( A + X ) 2 = A 2 + 2 ∗ A ∗ X + X 2 (A+X)^2=A^2+2*A*X+X^2 (A+X)2=A2+2AX+X2

很容易得到。如果该区间有两个数呢?令这一个数为 B B B易得

( A + X ) 2 + ( B + X ) 2 = ( A 2 + B 2 ) + 2 ∗ X ∗ ( A + B ) + 2 ∗ X 2 (A+X)^2+(B+X)^2=(A^2+B^2)+2*X*(A+B)+2*X^2 (A+X)2+(B+X)2=(A2+B2)+2X(A+B)+2X2

S 1 S_1 S1 S 2 S_2 S2 S 2 ′ S_2' S2表示来替换可得

S 2 ′ = S 2 + 2 ∗ X ∗ S 1 + L ∗ X 2 S_2'=S_2+2*X*S_1+L*X^2 S2=S2+2XS1+LX2

可以类推到区间长度为N的情况

求立方和时采用同样的方法可以得到

S 3 ′ = S 3 + 3 ∗ X ∗ S 2 + 3 ∗ S 1 ∗ X 2 + L ∗ X 3 S_3'=S_3+3*X*S_2+3*S_1*X^2+L*X^3 S3=S3+3XS2+3S1X2+LX3

到目前为止可以得到操作一的处理方法

{ S 1 ′ = S 1 + L ∗ X S 2 ′ = S 2 + 2 ∗ X ∗ S 1 + L ∗ X 2 S 3 ′ = S 3 + 3 ∗ X ∗ S 2 + 3 ∗ S 1 ∗ X 2 + L ∗ X 3 \left\{ \begin{array}{c} S_1'=S_1+L*X \\ S_2'=S_2+2*X*S_1+L*X^2 \\ S_3'=S_3+3*X*S_2+3*S_1*X^2+L*X^3 \end{array} \right. S1=S1+LXS2=S2+2XS1+LX2S3=S3+3XS2+3S1X2+LX3

现在来讨论操作二的处理方法:

( K ∗ A ) 2 = K 2 ∗ A 2 (K*A)^2=K^2*A^2 (KA)2=K2A2 ( K ∗ A ) 3 = K 3 ∗ A 3 (K*A)^3=K^3*A^3 (KA)3=K3A3

{ S 1 ′ = K ∗ S 1 S 2 ′ = K 2 ∗ S 2 S 3 ′ = K 3 ∗ S 3 \left\{ \begin{array}{c} S_1'=K*S_1\\ S_2'=K^2*S_2\\ S_3'=K^3*S_3 \end{array} \right. S1=KS1S2=K2S2S3=K3S3

操作三是最简单:
相当于L个相同的数的平方和,立方和。

{ S 1 ′ = L ∗ X S 2 ′ = L ∗ X 2 S 3 ′ = L ∗ X 3 \left\{ \begin{array}{c} S_1'=L*X\\ S_2'=L*X^2\\ S_3'=L*X^3\\ \end{array} \right. S1=LXS2=LX2S3=LX3

因为都是区间操作所以我们惯用lazy标记来对子区间进行更新,我们定义三个lazy标记add,mult,to分别表示三种操作。最后一个问题是当一个区间内同时有三个标记时我们如何处理才能保证正确性。

毋庸置疑的是当存在第三种操作时优先执行这一步操作因为,存在这一步操作时已经将在这次操作之前的前两种操作都清零了,所以相对于其他两种操作,第三种操作是最早的,优先处理。同时将左右儿子的lazy更新,前两种操作清零,即

 tree[d<<1].lazyto=tree[d<<1|1].lazyto=x;
 tree[d<<1].lazymult=tree[d<<1|1].lazymult=1;
 tree[d<<1].lazyadd=tree[d<<1|1].lazymult=0;
 tree[d].lazyto=0;

前两种操作的先后顺序不好确定。
我们先观察这两种情况
先进行第一种操作再进行第二种操作
( S 1 + A ) ∗ B = B ∗ S 1 + A ∗ B (S_1+A)*B=B*S_1+A*B (S1+A)B=BS1+AB
先进行第二种操作再进行第一种操作
( B ∗ S 1 ) + A = B ∗ S 1 + A (B*S_1)+A=B*S_1+A (BS1)+A=BS1+A
观察可以发现这两种情况我们都可以转换成先乘后加的情况,那么乘法的优先级就比加法高,同时存在时先进行乘法,但传给儿子时要将加数乘上乘数。

 tree[d].lazyadd=((tree[d].lazyadd%MOD)*x)%MOD;
 tree[d].lazymult=((tree[d].lazymult%MOD)*x)%MOD;

综上所述优先级是
Ⅲ > Ⅱ > Ⅰ Ⅲ>Ⅱ>Ⅰ >>
至此题目结束剩下的就是要细心写就可以了。都是线段树的基本操作。
还要注意模数的操作。同时lazymult要从1开始。

附上代码:

#include<iostream>
#include<cmath>
#include<queue>
#include<map>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<set>
#include<stack>
#define ll long long 
#define MAXN 500010
#define MAXM 810
#define INF 0x3f3f3f3f
//9223372036854775807
using namespace std;

const int MOD=10007;
int n,q,p=0,tim=0,ans=0;
int fa[MAXN],h[MAXN],in[MAXN],out[MAXN];
int a[810][810];
struct Tree {
 int l,r;
 ll lazyadd,lazymult,lazyto;
 ll sum[4];
}tree[MAXN];

void build(int d,int l,int r) {
 tree[d].l=l;tree[d].r=r;
 tree[d].lazyadd=tree[d].lazyto=0;
 tree[d].lazymult=1;
 for(int i=1;i<=3;++i) tree[d].sum[i]=0;
 if(l==r)return;
 int mid=(tree[d].l+tree[d].r)>>1;
 build(d<<1,l,mid);
 build(d<<1|1,mid+1,r);
}

void op1(int d,ll x) {
 ll temp=x;
 ll len=(ll)(tree[d].r-tree[d].l+1)%MOD;
 ll a1,a2,a3;
 tree[d].sum[3]=((tree[d].sum[3]%MOD)+(((tree[d].sum[2]%MOD)*(temp%MOD)*3)%MOD)+((tree[d].sum[1]*((temp*temp)%MOD)*3)%MOD)+((len*((((temp*temp)%MOD)*temp)%MOD))%MOD))%MOD;
 tree[d].sum[2]=((tree[d].sum[2]%MOD)+((((tree[d].sum[1]*temp)%MOD)*2)%MOD)+((len*((temp*temp)%MOD))%MOD))%MOD;
 tree[d].sum[1]=((tree[d].sum[1]%MOD)+(len*temp)%MOD)%MOD;//注意顺序,因为要用到改之前的sum所以从3到1 
 tree[d].lazyadd=((tree[d].lazyadd%MOD)+(x%MOD))%MOD;
}

void op2(int d,ll x) {
 tree[d].sum[1]=(tree[d].sum[1]%MOD*x)%MOD;
 tree[d].sum[2]=((tree[d].sum[2]%MOD)*((x*x)%MOD))%MOD;
 tree[d].sum[3]=((tree[d].sum[3]%MOD)*(((x*x)%MOD)*x)%MOD)%MOD;
 tree[d].lazyadd=((tree[d].lazyadd%MOD)*x)%MOD;
 tree[d].lazymult=((tree[d].lazymult%MOD)*x)%MOD;
}

void op3(int d,ll x) {
 ll len=(tree[d].r-tree[d].l+1)%MOD;
 tree[d].sum[1]=(len*x)%MOD;
 tree[d].sum[2]=(len*((x*x)%MOD))%MOD;
 tree[d].sum[3]=(len*(((x*x)%MOD*x)%MOD))%MOD;
 tree[d].lazyto=x;
 tree[d].lazymult=1;
 tree[d].lazyadd=0;
}

void pushup(int d) {
 for(int i=1;i<=3;++i) tree[d].sum[i]=(tree[d<<1].sum[i]+tree[d<<1|1].sum[i])%MOD;
}

void pushdown(int d) {
 if(tree[d].lazyto) {
  op3(d<<1,tree[d].lazyto);
  op3(d<<1|1,tree[d].lazyto);
  tree[d].lazyto=0;
 }
 if(tree[d].lazymult!=1) {
  op2(d<<1,tree[d].lazymult);
  op2(d<<1|1,tree[d].lazymult);
  tree[d].lazymult=1;
 }
 if(tree[d].lazyadd) {
  op1(d<<1,tree[d].lazyadd);
  op1(d<<1|1,tree[d].lazyadd);
  tree[d].lazyadd=0;
 }
}

void modify(int d,int l,int r,ll x,int op) {
 if(tree[d].l==l&&tree[d].r==r) {
  if(op==1) {
   op1(d,x);
  }
  if(op==2) {
   op2(d,x);
  }
  if(op==3) {
   op3(d,x); 
  }
  return;
 }
 if(tree[d].lazyadd||tree[d].lazymult!=1||tree[d].lazyto) {
  pushdown(d);
 }
 int mid=(tree[d].l+tree[d].r)>>1;
 if(r<=mid) {
  modify(d<<1,l,r,x,op);
 }
 else {
  if(l>mid) {
   modify(d<<1|1,l,r,x,op);
  }
  else {
   modify(d<<1,l,mid,x,op);
   modify(d<<1|1,mid+1,r,x,op);
  }
 }
 pushup(d);
}

ll query(int d,int l,int r,int x) {
 if(tree[d].l==l&&tree[d].r==r) {
  return tree[d].sum[x]%MOD;
 }
 if(tree[d].lazyadd||tree[d].lazymult!=1||tree[d].lazyto) pushdown(d);
 int mid=(tree[d].l+tree[d].r)>>1;
 if(r<=mid) {
  return query(d<<1,l,r,x)%MOD;
 }
 else {
  if(l>mid) {
   return query(d<<1|1,l,r,x)%MOD;
  }
  else {
   return (query(d<<1,l,mid,x)+query(d<<1|1,mid+1,r,x))%MOD;
  }
 }
}

int main() {
 while(scanf("%d%d",&n,&q)!=EOF) {
  if(n==0&&q==0)break;
  build(1,1,n);
  for(int i=1;i<=q;++i) {
   int op,l,r;
   ll x;
   scanf("%d%d%d%lld",&op,&l,&r,&x);
   if(op==4) {
    printf("%lld\n",query(1,l,r,x));
   }
   else {
    modify(1,l,r,x,op);
   }
  }
 }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值