线段树之单点更新
好久没更了QAQ,感觉之前学的都忘光了,重新看一遍题目,一开始以为是区间更新的题目,后来才发现其实是单点更新,不过有一个筛选的过程,不然会超时。
Question:
Description:
A lot of battleships of evil are arranged in a line before the battle. Our commander decides to use our secret weapon to eliminate the battleships. Each of the battleships can be marked a value of endurance. For every attack of our secret weapon, it could decrease the endurance of a consecutive part of battleships by make their endurance to the square root of it original value of endurance. During the series of attack of our secret weapon, the commander wants to evaluate the effect of the weapon, so he asks you for help.
You are asked to answer the queries that the sum of the endurance of a consecutive part of the battleship line.
Notice that the square root operation should be rounded down to integer.
Input:
The input contains several test cases, terminated by EOF.
For each test case, the first line contains a single integer N, denoting there are N battleships of evil in a line. (1 <= N <= 100000)
The second line contains N integers Ei, indicating the endurance value of each battleship from the beginning of the line to the end. You can assume that the sum of all endurance value is less than 2 63.
The next line contains an integer M, denoting the number of actions and queries. (1 <= M <= 100000)
For the following M lines, each line contains three integers T, X and Y. The T=0 denoting the action of the secret weapon, which will decrease the endurance value of the battleships between the X-th and Y-th battleship, inclusive. The T=1 denoting the query of the commander which ask for the sum of the endurance value of the battleship between X-th and Y-th, inclusive.
Output:
For each test case, print the case number at the first line. Then print one line for each query. And remember follow a blank line after each test case.
Sample Input:
10
1 2 3 4 5 6 7 8 9 10
5
0 1 10
1 1 10
1 1 5
0 5 8
1 4 8
Sample Output:
Case #1:
19
7
6
Answer:
本题需要维护的值是和,操作是对区间内所有的数开方向下取整,本来以为可以用区间维护,后来发现没办法直接维护和,和只能由左右子树相加得到结果,因此只能单点更新,但这样肯定会超时,因为最多达到100000次更新和询问,每一次都需要更新到叶子节点,有没有什么办法可以减少更新次数呢?答案是有的!这个题目要求的操作是开方向下取整,当区间内每个叶子节点的数小于等于1的时候,更新操作后的和不变,因此可以不用更新,而每个数不大于2^63,也就是说,最多可以开方7次,多余7次的操作都没有意义,因此,我们设置一个标记判断这个区间是否需要被更新,如果区间内每个数都小于等于1,则标记为false,否则标记为true,当标记为true时,才对区间内单点更新。最坏的情况下,我们需要对线段树进行7次更新,这个复杂度是可以接受的。
第一步:数据预处理
#define MAXN 100010
#define MAX 4294967297
第二步:自定义数据类型
typedef long long LL;
struct Tree
{
int left,right;
bool flag;//判断区间是否需要被更新
LL sum;
}tr[MAXN*4];
第三步:主函数
int main()
{
int N=0,t=1;
while(scanf("%d",&N)!=EOF)//多组输入
{
build(1,1,N);//建立线段树,在这里面输入数据
int Q;
printf("Case #%d:\n",t++);
scanf("%d",&Q);//更新及查询次数
while(Q--)
{
int order,l,r;
scanf("%d%d%d",&order,&l,&r);
if(l>r)//除错处理
{
int t=l;l=r;r=t;
}
if(order==0)
updata(1,l,r);//更新线段树
else
printf("%I64d\n",quary(1,l,r));//查询区间和
}
printf("\n");
}
return 0;
}
第四部:子函数
线段树的建立及其初始化函数:void build(int id,int l,int r)
void build(int id,int l,int r)
{
tr[id].left=l;
tr[id].right=r;
if(l==r)
{
scanf("%I64d",&tr[id].sum);
if(tr[id].sum<=1)
tr[id].flag=false;
else
tr[id].flag=true;
}
else
{
int mid=(l+r)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
tr[id].flag=tr[id*2].flag||tr[id*2+1].flag;//如果子树需要被更新,那么这个区间也需要被更新
}
}
线段树的更新函数:void updata(int id,int l,int r)
void updata(int id,int l,int r)
{
if(l<=tr[id].left&&r>=tr[id].right)
{
if(!tr[id].flag)//这个区间内的所有叶子节点的值都小于等于1,不需要被更新。这是减少更新次数的重要步骤
return;
else
{
if(tr[id].left==tr[id].right)
{
tr[id].sum=sqrt1(tr[id].sum);
if(tr[id].sum<=1)
tr[id].flag=false;//当通过开方使得叶子节点的值小于等于1时,记得改变flag的值
}
else
{
updata(id*2,l,r);
updata(id*2+1,l,r);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
tr[id].flag=tr[id*2].flag||tr[id*2+1].flag;//每次更新都要更新整个节点
}
}
}
else
{
int mid=(tr[id].left+tr[id].right)/2;
if(l<=mid)
updata(id*2,l,r);
if(r>mid)
updata(id*2+1,l,r);
tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
tr[id].flag=tr[id*2].flag||tr[id*2+1].flag;//每次更新都要更新整颗树
}
}
线段树查询函数:LL quary(int id,int l,int r)
LL quary(int id,int l,int r)
{
if(l<=tr[id].left&&r>=tr[id].right)
return tr[id].sum;
else
{
int mid=(tr[id].left+tr[id].right)/2;
LL ans=0;
if(l<=mid)
ans+=quary(id*2,l,r);
if(r>mid)
ans+=quary(id*2+1,l,r);
return ans;
}
}
开方并向下取整:LL sqrt1(LL y)
因为结果一定是整数且最大的数不超过2^63,因此采用二分查找是最快的方法,最多循环32次就可查到结果,常数量级不影响复杂度
LL sqrt1(LL y)
{
LL r=MAX;
LL l=0;
LL x=(l+r)/2;
while(x*x>y||(x+1)*(x+1)<=y)
{
if(x*x>y)
r=x;
if((x+1)*(x+1)<=y)
l=x;
x=(l+r)/2;
}
return x;
}
以上!各位童鞋要注意细节哦,很多时候就是那么一点细节就可以让你debug好几个小时(说的就是本人啦QAQ),所以一定要培养良好的编程习惯,有时或许很麻烦,不过在debug的时候很方便哦~
时空复杂度如下:
Memory | Time | Length |
---|---|---|
7588(Kb) | 1154(Ms) | 2014(Bytes) |
这一次就到此为止了~下一次也要加油!
噢!