HDU-4578 Transformation 线段树(两种方法)

题目大意

多组数据(n,m同为0时结束),每组第一行一个 n 表有n个整数,一个 m 表有m条操作(1<=n,m<=1e5)

接下来 m 行,每行4个整数(1<=x<=y<= n,1<= c<=1e4,1<= p<=3)

1 x y c 表示 x<=i<=y,ai+=c;

2 x y c 表示 x<=i<=y,ai*=c;

3 x y c 表示 x<=i<=y,ai=c;

4 x y p 表示 输出 x<=i<=y ,ai^p的和

解析

在解析之前,我们先来复习一下基本的求和线段树(带lazy标记),然后讲一下我最初的错误思想

基础好且不感兴趣的客官可以考虑跳过这部分内容

首先是复习部分

线段树求和时,遇到完整可更新的结点立刻更新该结点值,并挂上lazy攒着没向下更新的值

不能整段更新则不能挂上lazy,然后把原来的lazy下放(down),更新子结点

递归到最后,再利用子节点更新父结点(up)

query的时候注意不需要up,因为即便有down的更新也是完整结点的更新

接着说说我的错误想法

p==1的时候非常好更新,然而p==2,p==3该怎么办呢,着眼点自然在这里

于是我开始思考了,先考虑p==2

对叶子结点的父结点有 sum2=x1^2+x2^2

而内部结点呢  sum2[rt]=sum2[lson]+sum2[rson]

于是一层一层的想,会不会有什么特别的关系直接能够更新当前结点呢

傻瓜一样的想法,自己写下的这种前提条件就不可能直接更新大结点,只能up

于是就想,那我们就不更新无脑攒lazy,只有要输出值的时候再一口气去掉lazy,down下去再up回来

真是糟糕的想法,这样和没lazy甚至都快要差不多了(不过听说有人用这个写7500ms卡过去了。。。似乎不优化常数铁定过不了)

怎么说呢,不要有奇怪且错误的想法,还是应当往线段树原有的lazy的功能上去想,才不会使思路跑偏

下面开始是正经解析

本道题关键点与难点就在于update,搞定了,本题就过了

关于update,想一想普通求和线段树就会意识到最关键的点在于,如何直接对当前的完整结点更新

对于普通求和我们是 sum+=(r-l+1)*c,换个写法就是 sum=sum+(r-l+1)*c

于是我们便意识到这种更新就是找 老sum 与 新sum 的关系

而你用 sum[rt]=sum[lson]+sum[rson] 去想是没意义的,我们根本不需涉及子结点

于是怎么办呢,写出sum的最简式来考虑,就不难发现规律了

sum1=x1+x2+...+xn

sum2=x1^2+x2^2+...+xn^2

sum3=x1^3+x2^3+...+xn^3

操作1(+=c):

sum1'=(x1+c)+(x2+c)+...+(xn+c) = sum1+nc

sum2'=(x1+c)^2+(x2+c)^2+...+(xn+c)^2

          =x1^2+x2^2+...+xn^2+2c(x1+x2+...+xn)+nc^2

          =sum2+2csum1+nc^2

sum3'=(x1+c)^3+(x2+c)^3+...+(xn+c)^3

           =x1^3+x2^3+...+xn^3+3c(x1^2+x2^2+...+xn^2)+3c^2(x1+x2+...+xn)+nc^3

           =sum3+3csum2+3c^2sum1+nc^3

操作2(*=c):(比较简单了,不写详细过程了)

sum1''=csum1      sum2''=c^2sum2       sum3''=c^3sum3

操作3(=c):

sum1'''=nc       sum2'''=nc^2    sum3'''=nc^3

然后需要注意的一点是,写代码的时候操作1 要先更sum3,再sum2,然后sum1

因为这样就能保证等式右侧用的是旧的 sum1 sum2 sum3

如果你不愿意这样写的话也可以自己整个临时变量也行

那么关于lazy的部分,仔细想一下,似乎还有顺序问题,该怎么办呢,比如 +c1 *c2

先+后*则  x1^2  变成   (c2x1+c1c2)^2         先*后+    则为 (c2x1+c1)^2   

难道还要记录先后顺序吗?没必要!

如果是先来的 +c2   那么把 对应的lazy乘一下变成 lazy1*c1 即 c1*c2

用的时候 x1先做乘 再做加就好

对于操作三赋值的部分呢?读者可以自己想一下,也不难发现应该怎么做

于是结论如下:

操作1:lazy1+=c

操作2:lazy1*=c  lazy2*=c

操作3:lazy1=0   lazy2=1    lazy3=c

这里千万要注意 lazy2 要初始化为 1,博主就犯了错误初始化为0了

对操作2 lazy2*=c 要是0的话怎么*都是0,不过非要用0也不是不可以,特判一下就可以了

但是 很容易犯错,请一定要注意

然后 down 的时候什么样的顺序 用lazy去更新子结点

我们知道操作3更新时 lazy1,2被初始化了,于是此时若 lazy1,2有值,则是lazy3之后 后来的

于是down的时候

先更 lazy3 再更 lazy2 最后更 lazy1

然后此处也有非常重要的一点,把当前lazy给到子结点lazy的时候

因为子结点lazy可能已经有过值,一定不要把父节点lazy就直接赋给了子结点lazy

一定要按正常套路再去算一遍

不得不说这道题细节是真的多

说到细节。。博主不小心犯了个傻瓜式错误,线段树空间忘开四倍了。。。。

结果报错居然报的不是RE而是TLE。。debug了半天。。。真的怀疑人生,为什么会犯这种错误。。。

然后关于更新,我看网上好多题解都是 lazy1,2混更,我不知道为什么要把代码和思路都搞的那么乱,反正三个依次来就好

这些搞定了这道题基本就搞定了,客官不需要下面的代码也足以自己写出来了

说实话这个题代码量还是挺大的,我的代码写的也蛮乱套的(笑哭),不想看的话就不看也行,反正知道了思路就可以自己写了

代码如下(为了使代码看着更清爽,加了些引用):

#pragma GCC optimize("Ofast")
#include 
#define M 10007
#define maxn 100005
using namespace std;
int n,m,op,x,y,c,tree1[maxn<<2],tree2[maxn<<2],tree3[maxn<<2],lazy1[maxn<<2],lazy2[maxn<<2],lazy3[maxn<<2];
void build(int l,int r,int rt){
	tree1[rt]=tree2[rt]=tree3[rt]=lazy1[rt]=lazy3[rt]=0;
	lazy2[rt]=1;
	if(l==r) return;
	build(l,(l+r)/2,rt<<1);
	build((l+r)/2+1,r,rt<<1|1);
}
void down(int l,int r,int rt){
	int ln=(l+r)/2-l+1,rn=r-(l+r)/2,c1=lazy1[rt],c2=lazy2[rt],c3=lazy3[rt];
	int &l1=tree1[rt<<1],&l2=tree2[rt<<1],&l3=tree3[rt<<1];
	int &r1=tree1[rt<<1|1],&r2=tree2[rt<<1|1],&r3=tree3[rt<<1|1];
	if(c3){
		l1=ln*c3%M,r1=rn*c3%M;
		l2=ln*c3%M*c3%M,r2=rn*c3%M*c3%M;
		l3=ln*c3%M*c3%M*c3%M,r3=rn*c3%M*c3%M*c3%M;
		lazy1[rt<<1]=lazy1[rt<<1|1]=0;
		lazy2[rt<<1]=lazy2[rt<<1|1]=1;
		lazy3[rt<<1]=lazy3[rt<<1|1]=c3;
	}
	if(c2>1){
		l1=l1*c2%M,r1=r1*c2%M;
		l2=l2*c2%M*c2%M,r2=r2*c2%M*c2%M;
		l3=l3*c2%M*c2%M*c2%M,r3=r3*c2%M*c2%M*c2%M;
		lazy1[rt<<1]=lazy1[rt<<1]*c2%M,lazy1[rt<<1|1]=lazy1[rt<<1|1]*c2%M;
		lazy2[rt<<1]=lazy2[rt<<1]*c2%M,lazy2[rt<<1|1]=lazy2[rt<<1|1]*c2%M;
	}
	if(c1){
		l3=(l3+3*c1*l2%M+3*c1*c1%M*l1%M+ln*c1%M*c1%M*c1%M)%M;
		r3=(r3+3*c1*r2%M+3*c1*c1%M*r1%M+rn*c1%M*c1%M*c1%M)%M;
		l2=(l2+2*c1*l1%M+ln*c1%M*c1%M)%M;
		r2=(r2+2*c1*r1%M+rn*c1%M*c1%M)%M;
		l1=(l1+ln*c1%M)%M;
		r1=(r1+rn*c1%M)%M;
		lazy1[rt<<1]=(lazy1[rt<<1]+c1)%M,lazy1[rt<<1|1]=(lazy1[rt<<1|1]+c1)%M;
	}
	lazy1[rt]=lazy3[rt]=0,lazy2[rt]=1;
}
void up(int rt){
	tree1[rt]=(tree1[rt<<1]+tree1[rt<<1|1])%M;
	tree2[rt]=(tree2[rt<<1]+tree2[rt<<1|1])%M;
	tree3[rt]=(tree3[rt<<1]+tree3[rt<<1|1])%M;
}
void update(int op,int x,int y,int c,int l,int r,int rt){
	if(ry) return;
	if(x<=l&&r<=y){
		int &t1=tree1[rt],&t2=tree2[rt],&t3=tree3[rt],len=r-l+1;
		if(op==1){
			t3=(t3+3*c*t2%M+3*c*c%M*t1%M+len*c%M*c%M*c%M)%M;
			t2=(t2+2*c*t1%M+len*c%M*c%M)%M;
			t1=(t1+len*c%M)%M;
			lazy1[rt]=(lazy1[rt]+c)%M;
		}
		else if(op==2){
			t1=t1*c%M;
			t2=t2*c%M*c%M;
			t3=t3*c%M*c%M*c%M;
			lazy1[rt]=lazy1[rt]*c%M,lazy2[rt]=lazy2[rt]*c%M;
		}
		else{
			t1=len*c%M;
			t2=len*c%M*c%M;
			t3=len*c%M*c%M*c%M;
			lazy1[rt]=0,lazy2[rt]=1,lazy3[rt]=c;
		}
		return;
	}
	down(l,r,rt);
	update(op,x,y,c,l,(l+r)/2,rt<<1);
	update(op,x,y,c,(l+r)/2+1,r,rt<<1|1);
	up(rt);
}
int query(int x,int y,int p,int l,int r,int rt){
	if(ry) return 0;
	if(x<=l&&r<=y) switch(p){
		case 1:return tree1[rt];
		case 2:return tree2[rt];
		case 3:return tree3[rt];
	}
	down(l,r,rt);
	return (query(x,y,p,l,(l+r)/2,rt<<1)+query(x,y,p,(l+r)/2+1,r,rt<<1|1))%M;
}
int main(){
	while(scanf("%d%d",&n,&m),n||m){
		build(1,n,1);
		while(m--){
			scanf("%d%d%d%d",&op,&x,&y,&c);
			if(op==4) printf("%d\n",query(x,y,c,1,n,1));
			else update(op,x,y,c,1,n,1);
		}
	}
	return 0;
}

法二:

第二种方法相对来讲各方面都占有优势,代码量很小,速度很快,空间复杂度也很小

但是算是一种投机取巧的方法

很像染色但不是标准染色,能想到这么做也真是不容易

不过再怎么说也是投机取巧,换个题说不好就会 T

这个题毕竟初始全是0,每次更也是一大段一更全是相同的,所以才会有染色的可能性

具体不多解释了,因为代码很简单,直接看代码就好

#pragma GCC optimize("Ofast")
#include 
#define M 10007
using namespace std;
int n,m,op,x,y,c,tree[100005<<2];
void down(int rt){
	if(tree[rt]==-1) return;
	tree[rt<<1]=tree[rt<<1|1]=tree[rt];
	tree[rt]=-1;
}
void subupdate(int op,int c,int l,int r,int rt){
	if(tree[rt]>=0){
		if(op==1) tree[rt]=(tree[rt]+c)%M;
		else tree[rt]=tree[rt]*c%M;
		return;
	}
	subupdate(op,c,l,(l+r)/2,rt<<1);
	subupdate(op,c,(l+r)/2+1,r,rt<<1|1);
}
void update(int op,int x,int y,int c,int l,int r,int rt){
	if(ry) return;
	if(x<=l&&r<=y){
		if(op==3) tree[rt]=c;
		else subupdate(op,c,l,r,rt);
		return;
	}
	down(rt);
	update(op,x,y,c,l,(l+r)/2,rt<<1);
	update(op,x,y,c,(l+r)/2+1,r,rt<<1|1);
}
int query(int x,int y,int p,int l,int r,int rt){
	if(ry) return 0;
	if(tree[rt]>=0){
		int ret=min(y,r)-max(x,l)+1;
		for(int i=1;i<=p;i++) ret=ret*tree[rt]%M;
		return ret;
	}
	return (query(x,y,p,l,(l+r)/2,rt<<1)+query(x,y,p,(l+r)/2+1,r,rt<<1|1))%M;
}
int main(){
	while(scanf("%d%d",&n,&m),n||m){
		tree[1]=0;
		while(m--){
			scanf("%d%d%d%d",&op,&x,&y,&c);
			if(op==4) printf("%d\n",query(x,y,c,1,n,1));
			else update(op,x,y,c,1,n,1);
		}
	}
	return 0;
}

 

你可能感兴趣的:(HDU-4578 Transformation 线段树(两种方法))