杭电ACM-LCY算法进阶培训班-专题训练(线段树)

杭电ACM-LCY算法进阶培训班-专题训练(线段树)

传送门

张煊的金箍棒(2)

Problem Description

张煊的金箍棒升级了!

升级后的金箍棒是由几根相同长度的金属棒连接而成(最开始都是铜棒,从1到N编号);

张煊作为金箍棒的主人,可以对金箍棒施以任意的变换,每次变换操作就是将一段连续的金属棒(从X到Y编号)改为铜棒,银棒或金棒。

金箍棒的总价值计算为N个金属棒的价值总和。其中,每个铜棒价值为1;每个银棒价值为2;每个金棒价值为3。

现在,张煊想知道多次执行操作后的金箍棒总价值。

Input

输入的第一行是测试数据的组数(不超过10个)。

对于每组测试数据,第一行包含一个整数N(1 <= N <= 100000),表示金箍棒有N节金属组成,第二行包含一个整数Q(0 <= Q <= 100,000),表示执行变换的操作次数。

接下来的Q行,每行包含三个整数X,Y,Z(1 <= X <= Y <= N,1 <= Z <= 3),它定义了一个操作:将从X到Y编号的金属棒变换为金属种类Z,其中Z = 1代表铜棒,Z = 2代表银棒,Z = 3代表金棒。

Output

对于每组测试数据,请输出一个数字,表示操作后金箍棒的总价值。

每组数据输出一行。

Sample Input

1
10
2
1 5 2
5 9 3

Sample Output

24

这道题需要进行区间修改,用差分是不行的,因为它只在区间的起点和终点做标记。这个标记很可能在后续的修改中被覆盖掉,无法计算出结果。
线段树也有区间修改的功能,并且这个修改不是仅仅在区间两端标记,而是可以落实在区间内每个叶子结点上。为了节省修改时间,线段树采用了“延时标记”的方法。
延时标记,就是如果对某个区间进行整体的修改,暂且不去真正的修改每个结点,而是给整个区间做个标记。之后,如果要查询区间内的某个结点的值,再根据之前的标记来计算这个具体结点的值。如果永远都没有查询区间内具体的值,那这个标记也一直不会去“落实”。

#include<cstdio>
using namespace std;
const int maxn=1e5+5;
struct SegmentTree{
    int l,r,v,add;  //v代表区间内价值总和
}tree[maxn*4];
int T,n,m,a,b,c;

void build(int p,int l,int r){
    tree[p].l=l,tree[p].r=r;
    if(l==r){tree[p].v=0;   return;}
    int mid=(l+r)/2;
    build(p*2,l,mid),build(p*2+1,mid+1,r);
    tree[p].v=tree[p*2].v+tree[p*2+1].v;
}

void spread(int p){
    if(tree[p].add){
        tree[p*2].v=(tree[p*2].r-tree[p*2].l+1)*tree[p].add;
        tree[p*2+1].v=(tree[p*2+1].r-tree[p*2+1].l+1)*tree[p].add;
        tree[p*2].add=tree[p].add;
        tree[p*2+1].add=tree[p].add;
        tree[p].add=0;
    }
}

void change(int p,int l,int r,int d){
    if(l<=tree[p].l&&tree[p].r<=r){
        tree[p].v=d*(tree[p].r-tree[p].l+1);
        tree[p].add=d;  return;
    }
    spread(p);
    int mid=(tree[p].l+tree[p].r)/2;
    if(l<=mid)  change(p*2,l,r,d);
    if(r>mid)   change(p*2+1,l,r,d);
    tree[p].v=tree[p*2].v+tree[p*2+1].v;
}

int ask(int p,int l,int r){
    if(l<=tree[p].l&&tree[p].r<=r)  return tree[p].v;
    spread(p);
    int mid=(tree[p].l+tree[p].r)/2;
    int sum=0;
    if(l<=mid)  sum+=ask(p*2,l,r);
    if(r>mid)   sum+=ask(p*2+1,l,r);
    return sum;
}

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        build(1,1,n),change(1,1,n,1);
        while(m--)  scanf("%d%d%d",&a,&b,&c),change(1,a,b,c);
        printf("%d\n",ask(1,1,n));
    }
}

I Hate It

Problem Description

很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。

Input

本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取’Q’或’U’) ,和两个正整数A,B。
当C为’Q’的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为’U’的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。

Output

对于每一次询问操作,在一行里面输出最高成绩。

Sample Input

5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

Sample Output

5
6
5
9

Hint

Huge input,the C function scanf() will work better than cin

这题需要维护区间最值,用到了线段树的“单点修改”、“区间查询”两个功能。ST表也可以查询区间最值,但是它不支持修改;树状数组一般用于维护前缀和,也不适用于这道题。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=2e5+5;
struct SegmentTree{
    int l,r,v;
}tree[maxn*4];

int n,m,op,a,b,c[maxn];

void build(int p,int l,int r){
    tree[p].l=l,tree[p].r=r;
    if(l==r){tree[p].v=c[l];return;}
    int mid=(l+r)/2;
    build(p*2,l,mid),build(p*2+1,mid+1,r);
    tree[p].v=max(tree[p*2].v,tree[p*2+1].v);
}

void change(int p,int x,int d){
    if(tree[p].l==tree[p].r){tree[p].v=d;return;}
    int mid=(tree[p].l+tree[p].r)/2;
    if(x<=mid)  change(p*2,x,d);
    else    change(p*2+1,x,d);
    tree[p].v=max(tree[p*2].v,tree[p*2+1].v);
}

int ask(int p,int l,int r){
    if(l<=tree[p].l&&tree[p].r<=r)  return tree[p].v;
    int mid=(tree[p].l+tree[p].r)/2;
    int val=0;
    if(l<=mid)  val=max(val,ask(p*2,l,r));
    if(mid<r)   val=max(val,ask(p*2+1,l,r));
    return val;
}

int main(){
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++)   scanf("%d",&c[i]);
        build(1,1,n);
        while(m--){
            scanf(" %c%d%d",&op,&a,&b); //%c前加空格
            if(op=='Q') printf("%d\n",ask(1,a,b));
            else    change(1,a,b);
        }
    }
}

LCIS

Problem Description

Given n integers.
You have two operations:
U A B: replace the Ath number by B. (index counting from 0)
Q A B: output the length of the longest consecutive increasing subsequence (LCIS) in [a, b].

Input

T in the first line, indicating the case number.
Each case starts with two integers n , m(0<n,m<=105).
The next line has n integers(0<=val<=105).
The next m lines each has an operation:
U A B(0<=A,n , 0<=B=105)
OR
Q A B(0<=A<=B< n).

Output

For each Q, output the answer.

Sample Input

1
10 10
7 7 3 3 5 9 9 8 1 8 
Q 6 6
U 3 4
Q 0 1
Q 0 5
Q 4 7
Q 3 5
Q 0 2
Q 4 6
U 6 10
Q 0 9

Sample Output

1
1
4
2
3
1
2
5

题意:对一个序列,有以下两种操作:

  1. 修改某个元素的值。
  2. 查询一段区间的最长连续上升子序列。

需要在线段树中额外维护五个信息:区间最长上升子序列长度v,紧靠左端的区间最长上升子序列长度ll,紧靠右端的区间最长上升子序列长度rr,区间左端的值lv,区间右端的值rv。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
struct SegmentTree{
    int l,r,ll,rr,v,lv,rv;
}tree[maxn*4];
int n,m,c[maxn],op,a,b,T;

void up(int p){
    tree[p].v=max(tree[p*2].v,tree[p*2+1].v);
    tree[p].ll=tree[p*2].ll;
    tree[p].rr=tree[p*2+1].rr;
    tree[p].lv=tree[p*2].lv;
    tree[p].rv=tree[p*2+1].rv;
    if(tree[p*2].rv<tree[p*2+1].lv){
        if(tree[p*2].ll==tree[p*2].r-tree[p*2].l+1)
            tree[p].ll=tree[p*2].ll+tree[p*2+1].ll;
        if(tree[p*2+1].rr==tree[p*2+1].r-tree[p*2+1].l+1)
            tree[p].rr=tree[p*2].rr+tree[p*2+1].rr;
        tree[p].v=max(tree[p].v,tree[p*2].rr+tree[p*2+1].ll);
    }
}

void build(int p,int l,int r){
    tree[p].l=l,tree[p].r=r;
    if(l==r){
        tree[p].ll=tree[p].rr=tree[p].v=1;
        tree[p].lv=tree[p].rv=c[l]; return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid),build(p*2+1,mid+1,r);
    up(p);
}

void update(int p,int x,int v){
    if(tree[p].l==tree[p].r){tree[p].lv=tree[p].rv=v;return;}
    int mid=(tree[p].l+tree[p].r)/2;
    if(x<=mid)  update(p*2,x,v);
    else    update(p*2+1,x,v);
    up(p);
}

int query(int p,int l,int r){
    if(l<=tree[p].l&&tree[p].r<=r)  return tree[p].v;
    int mid=(tree[p].l+tree[p].r)/2;
    int ans=0;
    if(l<=mid)  ans=max(ans,query(p*2,l,r));
    if(r>mid)   ans=max(ans,query(p*2+1,l,r));
    if(l<=mid&&r>mid&&tree[p*2].rv<tree[p*2+1].lv)
        ans=max(ans,min(tree[p*2].rr,mid-l+1)+min(tree[p*2+1].ll,r-mid));
    return ans;
}

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)   scanf("%d",&c[i]);
        build(1,1,n);
        while(m--){
            scanf(" %c%d%d",&op,&a,&b);
            if(op=='Q') printf("%d\n",query(1,a+1,b+1));
            else    update(1,a+1,b);
        }
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_51864047

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值