题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4578
解题思路:
①各种标记,用到哪下放到哪。
肯定是能想到用各种标记维护的,关键是标记之间会相互影响。
现在有三个标记:
mtag维护乘法标记,初值为1
add维护加法标记,初值为0
isset维护区间是否用一个数覆盖
操作1,如果加法标记有数值,那么让它的值乘以乘法标记,这样下次计算时 先用掉乘法标记再用掉加法标记就不会错
操作2,正常执行
操作3,把加法乘法标记初始化为0和1
②我们可以快速得到一次方的区间和,那么2次方,3次方怎么快速得到呢。
原来只要再维护两棵树就行了鸭!
③有关区间修改和下放时如何根据各种标记更新区间和的问题(考数学公式喽)
假设维护三次方的和的数组为ttt[],二次为tt[],一次为t[]
标记下放前:a³+b³+c³+....
下放后每一项变为(a*x+y)
根据(a+b)³ = a³+b³+3ab²+3a²b
得出更新后ttt[] = ttt[]*x³ + (r-l+1)*y³+3t[]*y²+3*tt[]*y
由于ttt[]更新需要用到tt[],t[],所以这三棵树要从三次到一次倒着更新
同理,
二次方是tt[] = tt[]*x²+(r-l+1)*y²+2t[]xy
一次方是t[] = t[]*x + (r-l+1)*y
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=l+r>>1
#define debug(x) printf("----Line%s----\n",#x)
#define ls rt<<1
#define rs rt<<1|1
#define mod 10007
using namespace std;
const int N = 1e5+5;
ll add[N<<2],mtag[N<<2];
ll t[N<<2],tt[N<<2],ttt[N<<2];
ll isset[N<<2];
ll Pow(ll a,int b){
if (b==2) return a*a%mod;
if (b==3) return a*a%mod*a%mod;
}
void push_up(int rt)
{
t[rt] = (t[ls] + t[rs])%mod;
tt[rt] = (tt[ls] + tt[rs])%mod;
ttt[rt] = (ttt[ls] + ttt[rs])%mod;
}
void push_down(int rt,int len)
{
if (isset[rt]){
ll x = isset[rt];
isset[ls] = isset[rs] = isset[rt];
isset[rt] = 0;
mtag[ls] = mtag[rs] = 1;
add[ls] = add[rs] = 0;
ttt[ls] = (len-len/2)*Pow(x,3)%mod;
tt[ls] = (len-len/2)*Pow(x,2)%mod;
t[ls] = (len-len/2)*x%mod;
ttt[rs] = (len/2)*Pow(x,3)%mod;
tt[rs] = (len/2)*Pow(x,2)%mod;
t[rs] = (len/2)*x%mod;
}
if (mtag[rt]==1 && add[rt]==0) return ;
ttt[ls] = (ttt[ls]*Pow(mtag[rt],3)%mod + (len-len/2)*Pow(add[rt],3)%mod + 3*tt[ls]*Pow(mtag[rt],2)%mod*add[rt]%mod + 3*t[ls]*mtag[rt]%mod*Pow(add[rt],2)%mod)%mod;
ttt[rs] = (ttt[rs]*Pow(mtag[rt],3)%mod + len/2 *Pow(add[rt],3)%mod + 3*tt[rs]*Pow(mtag[rt],2)%mod*add[rt]%mod + 3*t[rs]*mtag[rt]%mod*Pow(add[rt],2)%mod)%mod;
tt[ls] = (tt[ls]*Pow(mtag[rt],2)%mod + (len-len/2)*Pow(add[rt],2)%mod + 2*t[ls]*add[rt]*mtag[rt])%mod;
tt[rs] = (tt[rs]*Pow(mtag[rt],2)%mod + len/2*Pow(add[rt],2)%mod + 2*t[rs]*add[rt]*mtag[rt])%mod;
t[ls] = (t[ls]*mtag[rt] + (len-len/2)*add[rt])%mod;
t[rs] = (t[rs]*mtag[rt] + (len/2)*add[rt])%mod;
mtag[ls] = (mtag[ls]*mtag[rt])%mod;
mtag[rs] = (mtag[rs]*mtag[rt])%mod;
add[ls] = (add[ls]*mtag[rt]%mod+add[rt])%mod;///这个标记也要先乘后加
add[rs] = (add[rs]*mtag[rt]%mod+add[rt])%mod;
add[rt] = 0;
mtag[rt] = 1;
}
void update(int L,int R,int op,ll x,int rt,int l,int r)
{
if (L<=l && r<=R){
if (op==1){///+
add[rt] = (add[rt]+x)%mod;
ttt[rt] = (ttt[rt] + Pow(x,3)*(r-l+1)%mod + 3*tt[rt]*x%mod + 3*t[rt]*Pow(x,2)%mod)%mod;
tt[rt] = (tt[rt] + Pow(x,2)*(r-l+1) + 2*t[rt]*x)%mod;
t[rt] = (t[rt] + (r-l+1)*x)%mod;
}
if (op==2){///*
mtag[rt] = (mtag[rt]*x)%mod;
add[rt] = (add[rt]*x)%mod; ///这个是保证先乘后加结果正确的关键
ttt[rt] = (ttt[rt]*Pow(x,3))%mod;
tt[rt] = (tt[rt]*Pow(x,2))%mod;
t[rt] = (t[rt]*x)%mod;
}
if (op==3){///覆盖
mtag[rt] = 1,add[rt] = 0;
ttt[rt] = (r-l+1)*Pow(x,3)%mod;
tt[rt] = (r-l+1)*Pow(x,2)%mod;
t[rt] = (r-l+1)*x%mod;
isset[rt] = x;
}
return ;
}
mid;
push_down(rt,r-l+1);
if (L<=m) update(L,R,op,x,lson);
if (R>m) update(L,R,op,x,rson);
push_up(rt);
}
ll query(int L,int R,ll x,int rt,int l,int r)
{
if (L<=l && r<=R){
if (x==1) return t[rt];
if (x==2) return tt[rt];
if (x==3) return ttt[rt];
}
push_down(rt,r-l+1);
ll ans = 0;
mid;
if (L<=m) ans = (ans+query(L,R,x,lson))%mod;
if (R>m) ans = (ans+query(L,R,x,rson))%mod;
return ans;
}
void build(int rt,int l,int r)
{
add[rt] = isset[rt] = 0;///万恶的初始化,我放if里面了
mtag[rt] = 1;
if (l==r){
t[rt] = tt[rt] = ttt[rt] = 0;
return ;
}
mid;
build(lson);
build(rson);
push_up(rt);
}
int main()
{
int n,q;
while (~scanf("%d %d",&n,&q),n||q){
build(1,1,n);
int op,l,r;
ll x;
while (q--){
scanf("%d %d %d %lld",&op,&l,&r,&x);
if (op!=4){
update(l,r,op,x,1,1,n);
}
else
printf("%lld\n",query(l,r,x,1,1,n)%mod);
}
}
return 0;
}
总结:
1.了解了线段树区间操作各个标记同时出现时维护的方法。
2.线段树初始化还是建议不要用build了,干脆主函数搞个for(1~4*n)初始化得了。