题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1698
题目大意:
有一只锚,这只锚由许多连续的棍子相连而成。
共有3种材质的棍子:铜、银、金,它们对应的权值分别是1、2、3
要执行一些操作,每一次操作会将一个连续区间内的棍子的材质改变为某一个材质。要求:在所有的操作完成之后,锚上所有棍子的总权值是多少?
思路:
区间 成段 更新问题
刚刚接触线段树,就碰到这个问题,折腾了好一段时间。
由于一直用数组来做。捣鼓了一下,终于写出了生涯中第一棵显式线段树,使用链式存储。然而,由于我的算法比较笨,每次更新到最底层的叶子, 又返回更新到根。果然还是超时了。
后来在网上学习到 Lazy(懒惰松弛) 的思想,就是我们不必每次彻底更新整棵树,只需要在刚好小于等于搜索区间的那些节点处做个标记(-1/1/2/3)就好了。-1 代表这个节点对应区间内的hooks有不止一种权值,1 代表这个节点对应区间的hooks的权值都是1,2代表这个节点对应区间的hooks的权值都是2,3代表...都是3。
接下来只剩下2个问题需要解决:
①.对给定区间进行Lazy更新
②.输出总权值
① 对给定区间进行更新:
假定要更新的区间是[fr,to) (算法中默认区间为左闭右开)
void update(int fr , int to , node *& t , int value);
(节点t对应的区间,以下简称为节点区间)
1. 若[fr,to) 与 节点区间不相交,或者节点区间的值 与 value 相等,就不用更新了。
2. 若[fr,to)恰好包括节点区间 而且 节点区间的值与value不等。则令 节点区间的值为value就好了。
3. 1, 2的情况都不满足的情况下:
① 若节点区间的值为-1,即节点区间内的hooks有不止一种权值。则递归update函数至其两个儿子。
② 若节点区间的值不为-1且又与value不相等。那么我们要进行push down 操作。push down 操作就是将节点的两个儿子节点的值设为节点的值。然后将节点的值 赋为 -1,递归两个子节点。为什么要这样做呢 ?直接把节点的值赋为value 可不可以呢 ?
当然不可以!
为说明问题所在,这里举一个简单的例子:
假设一棵简单的线段树,总区间[0,2),初始化每个基本节点的值为1。
现将[0,2)的区间赋为2。对其进行Lazy松弛得到下图1:
现在,我们尝试将区间[1,1]的值更新为3,对比有push down 和 没有push down 的情况:
没有 push down的情况:
由于没有push down,导致[0,1)的值被修改成了3,错误!
有push down的情况:
第一步:将2个子节点赋为2,再将父节点赋为-1
第二步:递归到两个子节点:
①. 递归到[0,1)后,由于[0,1)与[1,1]不相交。值不变仍为2
②. 递归到[1,2)后,由于[1,2)恰好能包括[1,1],值改为3
正确 !
② 输出总权值:
int print(node *& root)
很自然的,我们定义sum = 0,从根节点开始递归下去:
① 若当前节点的标记 tag = -1,说明它子节点的类型不唯一,递归它的两个子节点.
② 若当前节点的标记 tag != -1,说明它子节点的类型唯一,sum += 节点区间长度 x tag.
算法步骤:
① 初始化,构造线段树,我的代码中使用递归构造。 复杂度:O(N)
② k次更新操作,每次更新。复杂度:至多O(logN)
③ 递归树,累加有用的值。复杂度:O(N)
总的复杂度:O(N + klogN)
运行结果:
Accept
源程序:
#include <cstdio>
#include <stack>
using namespace std;
//本题中的线段树不再是完美二叉树,也不是完全二叉树 (因为允许最后一层的叶子可以不连续地排在左边).
//尝试显式定义线段树 !
int N;
struct node{
int l,r;
node* left, *right;
int tag;
node()
{
left = right = NULL;
tag = 1;
}
node(int l, int r)
{
left = right = NULL;
this->l = l;
this->r = r;
tag = 1;
}
};
// 递归构造线段树
void init(node * root) {
stack<node*>s;
s.push(root);
while(!s.empty())
{
node* tmp = s.top();
s.pop();
if(tmp->r - tmp->l == 1)
continue;
else
{
int L = tmp->l, R = tmp->r;
node* lchild = new node(L , (L+R)/2);
node* rchild = new node((L+R)/2 , R);
tmp->left = lchild;
tmp->right = rchild;
s.push(lchild);
s.push(rchild);
}
}
}
// 使用 lazy 技术 更新节点的值
// 注意:使用了 push down 技术
void update(int fr, int to , node *& t , int val) {
if(fr >= t->r || to <= t->l || t->tag == val)
return;
else if(fr <= t->l && to >= t->r)
{
t->tag = val;
}
else
{
if(t->tag == -1)
{
update(fr,to,t->left,val);
update(fr,to,t->right,val);
}
// push down
else
{
if(t->left)
t->left->tag = t->tag;
if(t->right)
t->right->tag = t->tag;
t->tag = -1;
update(fr,to,t->left,val);
update(fr,to,t->right,val);
}
}
}
int print(node*& root) {
int sum = 0;
stack<node*> s;
s.push(root);
while(!s.empty())
{
node* tmp = s.top();
s.pop();
if(tmp->tag == -1)
{
if(tmp->left)
s.push(tmp->left);
if(tmp->right)
s.push(tmp->right);
}
else
{
sum += (tmp->r-tmp->l)*tmp->tag;
}
}
return sum;
}
int main() {
int CASE, cnt = 1;
scanf("%d",&CASE);
while(CASE--)
{
int ope;
scanf("%d",&N);
// initialize the segment tree
node* root = new node(0,N);
init(root);
scanf("%d",&ope);
int fr,to,val;
for(int i = 0 ; i < ope ; i ++)
{
scanf("%d%d%d",&fr,&to,&val);
update(fr-1,to,root,val);
}
int sum = print(root);
printf("Case %d: The total value of the hook is %d.\n",cnt,sum);
cnt ++;
}
return 0;
}