hdu 1698 线段树区间更新

Just a Hook

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 16251    Accepted Submission(s): 8087


Problem Description
In the game of DotA, Pudge’s meat hook is actually the most horrible thing for most of the heroes. The hook is made up of several consecutive metallic sticks which are of the same length.



Now Pudge wants to do some operations on the hook.

Let us number the consecutive metallic sticks of the hook from 1 to N. For each operation, Pudge can change the consecutive metallic sticks, numbered from X to Y, into cupreous sticks, silver sticks or golden sticks.
The total value of the hook is calculated as the sum of values of N metallic sticks. More precisely, the value for each kind of stick is calculated as follows:

For each cupreous stick, the value is 1.
For each silver stick, the value is 2.
For each golden stick, the value is 3.

Pudge wants to know the total value of the hook after performing the operations.
You may consider the original hook is made up of cupreous sticks.
 

Input
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 10 cases.
For each case, the first line contains an integer N, 1<=N<=100,000, which is the number of the sticks of Pudge’s meat hook and the second line contains an integer Q, 0<=Q<=100,000, which is the number of the operations.
Next Q lines, each line contains three integers X, Y, 1<=X<=Y<=N, Z, 1<=Z<=3, which defines an operation: change the sticks numbered from X to Y into the metal kind Z, where Z=1 represents the cupreous kind, Z=2 represents the silver kind and Z=3 represents the golden kind.
 

Output
For each case, print a number in a line representing the total value of the hook after the operations. Use the format in the example.
 

Sample Input
  
  
1 10 2 1 5 2 5 9 3
 

Sample Output
  
  
Case 1: The total value of the hook is 24.
 

Source

#include<stdio.h>
#define N 100010
int a[N],b[N],c[N];
int main()
{
	int t,i,n,m,j,k,v,s;
	scanf("%d",&t);
	for(i=1;i<=t;i++)
	{
		scanf("%d%d",&n,&m);
		for(j=1;j<=m;j++)
			scanf("%d%d%d",&a[j],&b[j],&c[j]);
		s=0;
		for(j=1;j<=n;j++)
		{
			v=1;
		  for(k=m;k>=1;k--)
		  {
			  if(j>=a[k]&&j<=b[k])
			  {
				  v=c[k];
				  break;
			  }
		  }
		  s=s+v;
		}
		printf("Case %d: The total value of the hook is %d.\n",i,s);
	}
	return 0;
}

这题要用到LAZY标记,决定自己写一下LAZY标记

先说题意:一个连续的线段,材料可能为金银铜,数值对应3,2,1,一开始所有单元都是铜,所以整段的和就是n。然后多个修改操作,每次为x,y,z把区间[x,y]的每个单元都变为数值z。z的值为1,2,3。所有的修改操作做完后,输出整个线段的和

然后说一下LAZY思想的本质,就是“只做表面功夫,内部其实不合格,当某一次需要访问到内部的时候,再对内部处理”,这种偷懒的思想就能减少操作的次数,因为每次访问到线段树的深度不一定一样,不需要全部都处理掉。

这个思想是基于线段树的性质的,我们知道线段树的一个结点代表的区间其实包含了它的所有子孙后代结点的区间,所以按照普通的做法,更改了该结点整段区间的信息,其子孙后代的信息要全部改变。下面举一个例子说明这个简单的道理

一个线段树[1,2],那么它有两个孩子[1,1],[2,2]

初始建树的时候,每个单元的数值为1。第一次修改,使[1,2]区间内每个单元的数值变为2,第二次修改,使[2,2]区间内每个单元的数值为3,最后问[1,2]区间和

普通的思路(全部处理)

1.第一次修改,使[1,2]区间内每个单元的数值变为2------->[1,2]=4,[1,1]=2,[2,2]=2;

2.第二次修改,使[2,2]区间内每个单元的数值为3--------->[1,2]=5,[1,1]=2,[2,2]=3;

3.可以直接输出[1,2]区间和为5

可以看到,每个结点的信息都是准确无误的,这当然好,但是需要付出很多时间(当结点数很大时),下面看LAZY思想处理的结果

1.第一次修改,使[1,2]区间内每个单元的数值变为2------->[1,2]=4,[1,1]=1,[2,2]=1;

(这里就可以看到,其子孙后代的信息并没有修改,这个就是做“表面功夫”,因为[1,2]在两个孩子上面,一旦修改了[1,2]就不再往下走,而实际上下面的信息并不是准确的)

2.第二次修改,使[2,2]区间内每个单元的数值为3--------->[1,2]=x,[1,1]=2,[2,2]=3;

(这里为什么[1,2]=x,意思是我们现在并不能知道[1,2]的值,为什么?而[1,1]=2居然变准确了,为什么?而[2,2]=3也变准确了,为什么?)

3.需要处理一次才能得到[1,2]的和(看上面那点,我们并不知道[1,2]的值,就是要经过处理才能知道的)

我们就以本题为例,说一下怎么实现,看看怎么设置线段树的结点
struct node
{
  int a,b;  //表示该结点覆盖的区间
      int sum;   //该区间的和,可以省略
      int col;   //最重要的量,表示该结点(对应的区间)的数值类型,本题就是1,2,3,还有0,0的意义后面说
}
col的意义很重要,本题中数值类型只有1,2,3,col=1/2/3,表示该结点对应的区间全部是该种类型(及区间内所有单元都是),若col=0表示该区间内并不是清一色的全部是一种颜色,而是有两种或者多种颜色,至于具体的信息我们不知道,我们只知道不止一种颜色。

我们怎么计算一个区间的和?如果该区间都是一种颜色,sum=(b-a+1)*col,因为每个单元都是这个数值。但是如果该区间不止一个颜色,我们不能直接计算,只能依靠左右孩子的值,亲(sum)=左孩子(sum)+右孩子(sum),事实上即便是第一种情况我们也是可以这样计算的。
LAZY用于处理“由浅至深”的修改。例子说明问题

当前结点[2,4]的col是3,要进行一个修改[3,3]变为1。根据线段树的性质,我们知道要到达[3,3]是必须经过[2,4]的。

到达[2,4]时我们发现它的类型是3,我们的类型是1,说明[2,4]的纯洁性将被破坏(该区间内将不止一种颜色)。[2,4]的左右孩子分别为[2,3],[4,4],显然我们应该去左孩子那边,不用去右孩子那边。这时候有一个关键的处理,本来[2,4]是纯洁的为3,那么我们知道[4,4]也应该是3,当然[2,3]也应该是3,所以我们记录[2,3],[4,4]的类型为3,并且计算它们的区间和(其实记录类型已经足够了,不一定需要计算区间和,所以我们在上面结构体定义中就写到,sum这个域是可以省略的)

做完这个操作,可以往左孩子方向走了吗?还不行,记得把[2,3]的类型修改为0,表示[2,3]已经不纯洁了。

接着,我们来到了[2,3],它的左右孩子为[2,2],[3,3],我们显然是往右孩子走。由于上面说了[2,3]的类型为3,可以知道[2,2],[3,3]的类型也必定是3,所以同样的我们要记录[2,2],[3,3]的类型为3,并计算它的区间和,然后把[2,3]类型变为0,表示它不纯洁了

最后我们呢来到了[3,3],是我们的目标区间了,我们当然把[3,3]的类型改为1,然后计算它的区间和。接下来不需要再往下走了,也就是不需要再理会它的子孙后代,直接结束整个修改算法(虽然在这里[3,3]并没有孩子,但是其他有孩子的情况下也不要再往下走了,这个就是LAZY)
上面的例子说明了很多问题和具体的操作,但是还有一些情况我们没有讨论,接下来,我们就在上面的修改基础上,继续修改一次,以便说明这种情况。这次我们要把[2,2]变为类型2
同样的,我们必定经过[2,4],但这次情况有所不同了,我们看看[2,4]的类型是什么,是0!!!!!说明[2,4]并不纯洁,这个区间内不止一种颜色,那我们就没办法确定它的孩子的信息。所以这个时候我们什么都不用做,直接往左边走来到[2,3]。来到[2,3]发现[2,3]的类型也是0,说明它也不纯洁,也没办法确定它的孩子的信息,直接来到它的左孩子[2,2]。来到目标区间[2,2]就直接修改类型为2,并计算区间和
所以我们已经可以总结实现了
1.什么时候能计算区间和?只有当这个区间是纯洁的,我们就可以用公式sum=(b-a+1)*col。而双亲不纯洁也就是类型为0时,只能亲(sum)=左孩子(sum)+右孩子(sum),这个公式也不是随便用的,必须满足条件就是左孩子是纯洁的,右孩子是纯洁的(但是它们的类型可以不同,其实也可以知道必定不同),如果有一方不纯洁,那么就递归处理这一方,直达返回它的sum。递归一定有尽头,因为至少元结点[i,i]的类型是确定的,一定是纯洁的,非0的。

2.每次我们要修改一些区间,都从树根出发并去到目标区间,路径时唯一的(这是树最基本的性质),所以我们经过哪些结点也是确定的。当我们沿着路径走(也就是经过这个目标区间的所有直属祖先),每到一个结点要看它是什么类型,如果是纯洁的,记得修改它的左右孩子的类型,为什么?因为它的左右孩子的类型不一定跟它的双亲结点的类型相同(而我们知道,它们应该是一样的),这个是为LAZY付出的代价,也就是终于检查到这个结点了,所以不得不修改它的左右孩子的信息。

3.有一个小剪枝:比如我们要修改[10,20]的类型为1,那么我们呢必定经过[1,30],而且[1,30]的类型为1,也就是说其子孙后代都是类型1,那么我们没必要往下走,因为这个修改是重复的。

节省一个域sum,重新写一个代码,包含了query函数,代码量更好了,空间更少了,时间更快了,更通俗易懂了

#include<stdio.h>
#define N 100010
struct node
{
    int x,y,col;
}a[N*3];
void creatree(int t,int x,int y)
{
    a[t].x=x;
    a[t].y=y;
    a[t].col=1;
    if(x==y)
        return ;
    int temp=t<<1;
    int mid=(x+y)>>1;
    creatree(temp,x,mid);
    creatree(temp+1,mid+1,y);
}
void update(int x,int y,int c,int t)
{
    if(a[t].col==c)
        return;
    //如果当前结点的类型和要修改的类型的类型相同那么没必要继续下去了
    if(a[t].x==x&&a[t].y==y)//找到目标区间
    {
        a[t].col=c; //修改目标区间的类型
        return;
    }
    int mid=(a[t].x+a[t].y)>>1;    
    int temp=t<<1;
    if(a[t].col)//当前结点的类型非0,即一整段区间是纯洁的单色的
    {
    
        a[temp].col=a[temp+1].col=a[t].col;//那么可知其左右孩子的类型和双亲是相同的
        a[t].col=0; //此时双亲的类型为0,表示不是单色不纯洁
    }
    if(x>mid) //去右孩子结点
        update(x,y,c,temp+1);
    else if(y<=mid) //去左孩子结点
        update(x,y,c,temp);
    else          //横跨两边
    {
        update(x,mid,c,temp);
        update(mid+1,y,c,temp+1);
    }
}
int query(int x,int y,int t)
{
    if(a[t].col)
     return (a[t].y-a[t].x+1)*(a[t].col);
    int mid=(x+y)>>1;
    int temp=t<<1;
    return query(x,mid,temp)+query(mid+1,y,temp+1);
}
int main()
{
 int t,i,cnt,n,m,b,c,d;
 scanf("%d",&t);
 cnt=1;
 while(t--)
 {
     scanf("%d",&n);
     creatree(1,1,n);
     scanf("%d",&m);
     for(i=1;i<=m;i++)
     {
         scanf("%d%d%d",&b,&c,&d);
         update(b,c,d,1);
     }
     printf("Case %d: The total value of the hook is %d.\n",cnt++,query(1,n,1));
 }
 return 0;
}

这题由于最后输出的是整个区间的和比较特殊,不需要另外的询问,因为每次修改都必定经过树根,在updata函数中维护好树根的sum值即可,最后直接输出
#include<stdio.h>
#define N 100010
struct node
{
    int x,y,sum,col;
}a[N*3];
void creatree(int t,int x,int y)
{
    a[t].x=x;
    a[t].y=y;
    a[t].sum=1;
    a[t].col=1;
    if(x==y)
        return ;
    int temp=t<<1;
    int mid=(x+y)>>1;
    creatree(temp,x,mid);
    creatree(temp+1,mid+1,y);
    a[t].sum=a[temp].sum+a[temp+1].sum;
}
void update(int x,int y,int c,int t)
{
    if(a[t].col==c)
        return;
 //如果当前结点的类型和要修改的类型的类型相同那么没必要继续下去了
    if(a[t].x==x&&a[t].y==y) //找到目标区间
    {
        a[t].col=c;//修改目标区间的类型
        a[t].sum=(y-x+1)*c; //计算目标区间的区间和
        return;
    }
    int mid=(a[t].x+a[t].y)>>1;    
    int temp=t<<1;
    if(a[t].col) //当前结点的类型非0,即一整段区间是纯洁的单色的
    {
    
        a[temp].col=a[temp+1].col=a[t].col;
      //那么可知其左右孩子的类型和双亲是相同的
       a[temp].sum=(a[temp].y-a[temp].x+1)*a[temp].col;//计算左孩子的区间和
       a[temp+1].sum=(a[temp+1].y-a[temp+1].x+1)*a[temp+1].col;//计算右孩子的区间和
        a[t].col=0;//此时双亲的类型为0,表示不是单色不纯洁
    }
    if(x>mid) //去右孩子结点
        update(x,y,c,temp+1);
    else if(y<=mid)//去左孩子结点
        update(x,y,c,temp);
    else//横跨两边
    {
        update(x,mid,c,temp);
        update(mid+1,y,c,temp+1);
    }
    a[t].sum=a[temp+1].sum+a[temp].sum;
}
int main()
{
 int t,i,cnt,n,m,b,c,d;
 scanf("%d",&t);
 cnt=1;
 while(t--)
 {
     scanf("%d",&n);
     creatree(1,1,n);
     scanf("%d",&m);
     for(i=1;i<=m;i++)
     {
         scanf("%d%d%d",&b,&c,&d);
         update(b,c,d,1);
     }
     printf("Case %d: The total value of the hook is %d.\n",cnt++,a[1].sum);
 }
 return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值