线段树可以说是每次比赛中必出的题了,但是线段树好难,我太难了,我上辈子一定是一道线段树的题。
下面是一些基础线段树的题目,入门必备。。。
线段树是一种二叉搜索树,将原始数据都存在叶节点,依次表示出每个叶节点的根节点。
一般数组开到叶节点数量的4倍。
关于线段树的例题
。HDU1166 敌兵布阵
按照指示来就可以
代码
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
#define M 50005
int n;
int a[M];
int sum[M<<2],add[M<<2];//n<<2表示n*4;
void PushUp(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];//n<<1|1表示n*2+1;根据子节点求根节点
}
void Build(int l,int r,int rt)
{
if(l == r){//指到达叶节点
scanf("%d",&sum[rt]);//输入叶节点
return;
}
int m=(l+r)>>1;
Build(l,m,rt<<1);//建立左支树
Build(m+1,r,rt<<1|1);//建立右支树
PushUp(rt);
}
void Update(int L,int C,int l,int r,int rt)//L为要修改的位置,C指要修改的值
{
if(l == r){
sum[rt]+=C;
return;
}
int m=(l+r)>>1;
if(L <= m) Update(L,C,l,m,rt<<1);
else Update(L,C,m+1,r,rt<<1|1);
PushUp(rt);
}
int Query(int L,int R,int l,int r,int rt)
{
if(L <= l&&r <= R){//若要查询的值在范围内,直接返回
return sum[rt];
}
int m=(l+r)>>1;
int ans=0;
if(L <= m) ans+=Query(L,R,l,m,rt<<1);
if(R > m) ans+=Query(L,R,m+1,r,rt<<1|1);
return ans;
}
int main()
{
int t,k=0;
scanf("%d",&t);
while(t--){
printf("Case %d:\n",++k);
scanf("%d",&n);
Build(1,n,1);
char op[10];
while(scanf("%s",op)){
if(op[0] == 'E') break;
int i,j;
scanf("%d%d",&i,&j);
if(op[0] == 'Q'){
int ans=Query(i,j,1,n,1);
printf("%d\n",ans);
}
else if(op[0] == 'S'){
Update(i,-j,1,n,1);
}
else{
Update(i,j,1,n,1);
}
}
}
return 0;
}
。HDU1754 I Hate It
同样按照要求即可
不同的是上一题是求和,而这一题是要求求出最大的数,所以在根据子节点求根节点的时候要用max
代码
#include <stdio.h>
#include <algorithm>
using namespace std;
#define M 200005
int sum[M<<2];
void PushUp(int rt)
{
sum[rt]=max(sum[rt<<1],sum[rt<<1|1]);//将子节点中较大的值给根节点
}
void Build(int l,int r,int rt)
{
if(l == r){
scanf("%d",&sum[rt]);
}
else{
int m=(l+r)>>1;
Build(l,m,rt<<1);
Build(m+1,r,rt<<1|1);
PushUp(rt);
}
}
void Update(int L,int C,int l,int r,int rt)
{
if(l == r){
sum[rt]=C;//根据题中要求,是将某个位置的值改变为多少,并不是增加或减少
}
else{
int m=(l+r)>>1;
if(L <= m) Update(L,C,l,m,rt<<1);
else Update(L,C,m+1,r,rt<<1|1);
PushUp(rt);
}
}
int Query(int L,int R,int l,int r,int rt)
{
if(L <= l&&r <= R){
return sum[rt];
}
int m=(l+r)>>1;
int ans=0;
if(L <= m) ans=max(ans,Query(L,R,l,m,rt<<1));//求最大
if(R > m) ans=max(ans,Query(L,R,m+1,r,rt<<1|1));
return ans;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
Build(1,n,1);
for(int i=0;i<m;i++){
char c[3];//如果只用一个字符可能会造成越界,所以这用一个数组,可以保证输入时的正确
int a,b;
scanf("%s%d%d",c,&a,&b);
if(c[0] == 'Q'){
int ans=Query(a,b,1,n,1);
printf("%d\n",ans);
}
else if(c[0] == 'U'){
Update(a,b,1,n,1);
}
}
}
return 0;
}
。HDU1698 Just a Hook
题意:原来有n个数,都为1,现在对他们进行改变,每次操作输入x,y,z,代表把【x,y】的值全部改变为z,z可以是1,2,3。
最后输出n个数的和。
代码
#include <stdio.h>
#include <algorithm>
using namespace std;
#define M 100005
int sum[M<<2];
int init[M<<2];//用来存原始数据
void PushUp(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void Build(int l,int r,int rt)
{
if(l == r){
sum[rt]=init[l];//给叶节点赋值,都为1
return;
}
int m=(l+r)>>1;
Build(l,m,rt<<1);
Build(m+1,r,rt<<1|1);
PushUp(rt);
}
void Pushdown(int rt,int ln,int rn)//下推标记节点,ln,rn表示左子树,右子树的数据数量
{
if(init[rt]){
init[rt<<1]=init[rt];
init[rt<<1|1]=init[rt];
sum[rt<<1]=init[rt]*ln;
sum[rt<<1|1]=init[rt]*rn;
init[rt]=0;
}
}
void Update(int L,int R,int C,int l,int r,int rt)//要修改的是一个区间的值,所以要进行标记,L,R为该区间的左右端点
{
if(L <= l&&r <= R){
sum[rt]=C*(r-l+1);
init[rt]=C;
return;
}
int m=(l+r)>>1;
Pushdown(rt,m-l+1,r-m);//下推标记
if(L <= m) Update(L,R,C,l,m,rt<<1);
if(R > m) Update(L,R,C,m+1,r,rt<<1|1);
PushUp(rt);
}
int main()
{
int t,k=0;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
init[i]=1;
Build(1,n,1);
for(int i=0;i<m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(z == 1){
Update(x,y,1,1,n,1);
}
else if(z == 2){
Update(x,y,2,1,n,1);
}
else{
Update(x,y,3,1,n,1);
}
}
int ans=sum[1];//sum[1]即为所有数的和
printf("Case %d: The total value of the hook is %d.\n",++k,ans);
}
return 0;
}
。codeforces 91B Queue
有n只海象排队,第一只站在最后的位置,第n只站在第一的位置(从左到右),第i只海象的年龄为ai,找站在他右边的海象中比他年轻的且离他最远的和他之间有多少只海象,(注意即使离他最远的比他年轻的海象和他之间有年纪比他大的也不会停,知道找到最右端那个比他小的),不存在输出-1。
还可以用vector做,单调队列,从第n个数开始遍历
代码
#include <stdio.h>
#include <algorithm>
using namespace std;
#define M 200005
#define maxn 100000005//定义一个很大的值来标记已经遍历过的数据
int sum[M<<2],a[M<<2];
void PushUp(int rt)
{
sum[rt]=min(sum[rt<<1],sum[rt<<1|1]);
}
void Build(int l,int r,int rt)
{
if(l == r){
scanf("%d",&sum[rt]);
a[l]=sum[rt];
return;
}
int m=(l+r)>>1;
Build(l,m,rt<<1);
Build(m+1,r,rt<<1|1);
PushUp(rt);
}
void Update(int L,int l,int r,int rt)
{
if(l == r){
sum[rt]=maxn;//表示这个值已经寻找过,每次只用遍历这个值右边的数据
return;
}
int m=(l+r)>>1;
if(L <= m) Update(L,l,m,rt<<1);
else Update(L,m+1,r,rt<<1|1);
PushUp(rt);
}
int Query(int x,int l,int r,int rt)
{
if(l == r){
return l;//返回最右端比他小的值的位置
}
int m=(l+r)>>1;
if(sum[rt<<1|1] < x) Query(x,m+1,r,rt<<1|1);
else Query(x,l,m,rt<<1);
}
int main()
{
int n;
scanf("%d",&n);
Build(1,n,1);
for(int i=1;i<=n;i++){
Update(i,1,n,1);
if(sum[1] >= a[i])
printf("%d%c",-1,i == n?'\n':' ');//如果这个数是最小的,便找不到比他小的
else
printf("%d%c",Query(a[i],1,n,1)-i-1,i == n?'\n':' ');//求他们之间数据的数量
}
return 0;
}