杭电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
题意:对一个序列,有以下两种操作:
- 修改某个元素的值。
- 查询一段区间的最长连续上升子序列。
需要在线段树中额外维护五个信息:区间最长上升子序列长度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);
}
}
}