线段树相关
1》 单点更新,区间求最值
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1754
题目大意: 有N个数储存为[1, N] 有M次操作
操作类型:
》 Q a b 表示[a, b] 内最大值
》 C a b 表示把a的值改为b
思路: 线段树单点更新 区间求最值。。
代码:这样超时了 证明递归太浪费时间。。。
#include<iostream>
#include<stdio.h>
using namespace std;
#define max(a, b) (a>b?a:b)
#define min(a, b) (a<b?a:b)
struct node
{
int left, right;
int maxx; //保存区间[left, right]的最大值
} stu[200001*4];
int s[200001];
void create(int i, int a, int b)//建树
{
stu[i].left = a;
stu[i].right = b;
if(a == b)
{
stu[i].maxx = s[a];
return ;
}
int mid = (a+b)/2;
create(i*2, a, mid);
create(i*2+1, mid+1, b);
stu[i].maxx = max(stu[i*2].maxx, stu[i*2+1].maxx);
}
void change(int i, int a, int b) //单点更新
{
if(stu[i].left==a && stu[i].right == a) //这样也可以if(stu[i].left == stu[i].right) 按条件递归下去肯定只能到a点
{
stu[i].maxx = b;
return ;
}
int mid = (stu[i].left + stu[i].right) /2;
if(a <= mid) change(i*2, a, b);
else change(i*2+1, a, b);
stu[i].maxx = max(b, stu[i].maxx); //当前区间最大值取 b 和 原来区间的最大值 中大的
}
int find1(int i, int a, int b)
{
if(stu[i].left ==a && stu[i].right == b) //找到区间返回最大值
return stu[i].maxx;
int mid = (stu[i].left+stu[i].right) / 2;
if(b<=mid) //完全在当前区间的左子区间
return find1(i*2, a, b);
else if(a>mid) //完全在当前区间的右子区间
return find1(i*2+1, a, b);
else //部分在左子区间,部分在右子区间,返回左子区间与右子区间中值大的
return max(find1(i*2, a, mid), find1(i*2+1, mid+1, b));
}
int main()
{
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
int i, num;
for(i=1; i<=n; i++)
scanf("%d", &s[i]);
create(1, 1, n);
while(m--)
{
char ch;
int a, b;
scanf("%*c%c%d%d", &ch, &a, &b);
if(ch == 'U') change(1, a, b);
if(ch == 'Q') printf("%d\n", find1(1, a, b));
}
}
return 0;
}
这样就OK了 对于find1 函数给一个标记值big记录最大值耗时少了一半~ ~
void find1(int i, int a, int b)
{
if(stu[i].left ==a && stu[i].right == b)
{
if(stu[i].maxx > big)
big = stu[i].maxx;
return ;
}
int mid = (stu[i].left+stu[i].right) / 2;
if(b<=mid) find1(i*2, a, b);
else if(a>mid) find1(i*2+1, a, b);
else
{
find1(i*2, a, mid);
find1(i*2+1, mid+1, b);
}
}
2》 区间更新 区间求和
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4267
题目大意: 有 N 个数存储为 [1,N]。 Q次操作。
操作类型:
》 C a b c 表示给区间[a, b]内每个数都加上 c
》 Q a b 表示求区间[a, b]内各个数的总和
思路: 线段树区间更新, 区间求和。。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
struct node
{
int l, r;
__int64 sum, flag; //sum代表区间[l, r]的总和 flag代表区间[l,r]内各个数的变化值
} s[100000*4];
int x[100005]; //辅助空间存储初始的 N 个数
void create(int i, int a, int b) // 建立树
{
s[i].l = a;
s[i].r = b;
s[i].flag = 0; //各个节点无变化 初始为0
if(a == b)
{
s[i].sum = x[a];
return ;
}
int mid = (a+b)/2;
create(i*2, a, mid);
create(i*2+1, mid+1, b);
s[i].sum = s[i*2].sum + s[i*2+1].sum; // 初始化时都为0这句可以不要~~
//父节点的总和值= 左子节点总和值 + 右子节点总和值
}
void updata(int i, int a, int b, __int64 c) //给区间[a, b]每个数都加上c
{
if(s[i].l == a && s[i].r == b)
{
s[i].flag += c; //给区间[a, b]的变化值 加c
s[i].sum += (b-a+1) * c; //qujian[a, b]各数加c
return ;
}
if(s[i].flag) //如果当前区间的变化值不为0, 则要对其子区间进行修改
{
s[i*2].flag += s[i].flag; //修改左子区间
s[i*2].sum += (s[i*2].r-s[i*2].l+1) * s[i].flag;
s[i*2+1].flag += s[i].flag; //修改右子区间
s[i*2+1].sum += (s[i*2+1].r-s[i*2+1].l+1) * s[i].flag;
s[i].flag = 0; //修改完成将当前区间变化值变为1
}
int mid = (s[i].r+s[i].l) /2;
if(a > mid) updata(i*2+1, a, b, c);
else if(b<=mid) updata(i*2, a, b, c);
else
{
updata(i*2, a, mid, c);
updata(i*2+1, mid+1, b, c);
}
s[i].sum = s[i*2].sum + s[i*2+1].sum; //当前区间总和值= 左子区间总和值+ 右子区间总和值
}
__int64 find1(int i, int a, int b) //返回区间[a, b]总和值
{
if(s[i].l == a && s[i].r == b) return s[i].sum;
if(s[i].flag) //这个在这里不能少!!!!!
{
s[i*2].flag += s[i].flag;
s[i*2].sum += (s[i*2].r-s[i*2].l+1) * s[i].flag;
s[i*2+1].flag += s[i].flag;
s[i*2+1].sum += (s[i*2+1].r-s[i*2+1].l+1) * s[i].flag;
s[i].flag = 0;
}
if(b <= s[i*2].r) //区间[a,b]完全在当前区间的左子区间
return find1(i*2, a, b);
else if(a>=s[i*2+1].l) //区间[a,b]完全在当前区间的右子区间
return find1(i*2+1, a, b);
else //区间[a,b]一部分在当前区间左。。 一部分在右。。
return find1(i*2, a, s[i*2].r) + find1(i*2+1, s[i*2+1].l, b);
}
int main()
{
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
int i, a, b;
__int64 c;
for(i=1; i<=n; i++)
scanf("%d", &x[i]);
create(1, 1, n);
while(m--)
{
char ch;
scanf("%*c%c", &ch);
if(ch == 'C')
{
scanf("%d%d%I64d", &a, &b, &c);
updata(1, a, b, c);
}
else
{
scanf("%d%d", &a, &b);
__int64 S = find1(1, a, b);
printf("%I64d\n", S);
}
}
}
return 0;
}
3》 单点更新 区间求和
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166
题目大意: 给你敌人营地个数N(<=50000),和每个营地的人数(<=50)。
有四种命令形式:
》 Add i j 表示第 i 营地增加 j 个人。
》 Sub i j 表示第 i 营地减少 j 个人。
》 Query i j 表示询问第 i 到第 j 个营地一共有多少人。
》 End 表示命令结束。
思路:线段树单点更新, 区间求和~ 代码有注释
代码:
#include<iostream>
#include<cstdio>
using namespace std;
typedef struct node //每个节点的结构
{
int left, right;//每个节点所对应区间 [left, right]
int data; //区间内所有节点的和值
} ss;
ss num[50005*4];
void create(int i, int a, int b)//初始化区间[a,b]
{
num[i].data = 0;
num[i].left = a;
num[i].right = b;
if(a == b) return ;
int mid = (a+b)/2;
create(2*i, a, mid);
create(2*i+1, mid+1, b);
}
void change(int i, int a, int b)//对于节点a进行更新 b个数量
{
if(num[i].left ==num[i].right )
{
num[i].data += b;
return ;
}
int mid = (num[i].left+num[i].right) / 2;
if(a <= mid) change(i*2, a, b); //节点a 在当前区间的左子区间
else change(i*2+1, a, b); //节点a 在当前区间的右子区间
num[i].data += b; //对于data 更新
}
int find1(int i, int a, int b){ //返回区间[a, b]的和值
if(num[i].left == a && num[i].right == b) return num[i].data;
int mid = (num[i].left+num[i].right) / 2;
if(mid < a) return find1(i*2+1, a, b); //区间[a, b] 在当前区间的右子区间
else if(mid >= b) return find1(i*2, a, b); //区间[a, b] 在当前区间的左子区间
else //区间[a, b] 一部分在当前区间的右子区间 一部分在左子区间
return find1(i*2, a, mid)+find1(i*2+1, mid+1, b);
}
int main()
{
int t, cas=1;
scanf("%d", &t);
while(t--)
{
printf("Case %d:\n", cas++);
int n, i, j;
scanf("%d", &n);
create(1, 1, n);
for(i=1; i<=n; i++)
{
scanf("%d", &j);
change(1, i, j);
}
while(1)
{
char str[100];
scanf("%s", str);
if(str[0] == 'Q')
{
scanf("%d%d", &i, &j);
int sum =find1(1, i, j);
printf("%d\n", sum);
}
if(str[0] == 'A')
{
scanf("%d%d", &i, &j);
change(1, i, j);
}
if(str[0] == 'S')
{
scanf("%d%d", &i, &j);
change(1, i, -j);
}
if(str[0] == 'E') break;
}
}
return 0;
}
》 区间最大连续值:
struct node
{
int R, L;
int Rs, Ls, ms;
};
说明:
左子区间[1,100] 右子区间[101, 150] 30,75,102, 114是断点
//父节点最大连续值 = max(左孩子的最大连续值,右孩子的最大连续值,左孩子右区间+右孩子左区间的连续值)
//左孩子的区间没有断点,则父节点的左区间=左孩子的右区间+右孩子的左区间
//右孩子与左孩子同理
左子区间:R==100, L==1, Ls==30-1, Rs==101-75, ms==74-30;
右子区间:R==150, L==101, Ls==101-75, Rs==150-114, ms==150-114;
父区间:R==150, L==1, Ls==30-1, Rs==150-114, ms==150-114
题意:
给一个连通的区间,有三种操作:
》 D x 把x点断开
》 Q x 问与x连通的点有多少
》 R 修复最后一次断开的点
代码:
#include <iostream> #include <cstdio> using namespace std; #define max(a, b) (a>b?a:b) struct node { int r, l; int rs, ls, ms;//rs右端点的最大连续值,ls左端点的最大连续值,ms区间的最大连续值 } s[50000<<2]; int Q[50000], top; //模拟栈 void create(int i, int l, int r) { s[i].l=l; s[i].r=r; s[i].ls=s[i].rs=s[i].ms=r-l+1;//开始时各个村落都是连接的 if(l==r) return ; int mid=(l+r)>>1; create(i*2, l, mid); create(i*2+1, mid+1, r); } void updata(int i, int a, int x) { if(s[i].l==s[i].r) //找到a村落 { if(x) s[i].ls=s[i].rs=s[i].ms=1; //修复 else s[i].ls=s[i].rs=s[i].ms=0; //破坏 return ; } int mid=(s[i].l+s[i].r)>>1; if(mid < a) updata(i*2+1, a, x); else updata(i*2, a, x); s[i].ls=s[i*2].ls; //父节点的左端点连续值 = 左孩子左端点的连续值 s[i].rs=s[i*2+1].rs; //父节点的右端点连续值 = 右孩子右端点的连续值 if(s[i*2].ms==s[i*2].r-s[i*2].l+1) //左孩子的区间没有断点,则父节点的左端点=左孩子的右端点+右孩子的左端点 s[i].ls += s[i*2+1].ls; if(s[i*2+1].ms==s[i*2+1].r-s[i*2+1].l+1) //右孩子同理 s[i].rs += s[i*2].rs; s[i].ms=max(max(s[i*2].ms, s[i*2+1].ms), (s[i*2].rs+s[i*2+1].ls)); //父节点最大连续值是左孩子的最大连续值,右孩子的最大连续值,左孩子右端点+右孩子左端点的连续值 } int find(int i, int a) { if(s[i].l==s[i].r || !s[i].ms || s[i].ms==(s[i].r-s[i].l+1)) //当前区间 最大长度空 或 无断点 或 找到目标 return s[i].ms; int mid=(s[i].r+s[i].l)>>1; if(a<=mid) { if(a> s[2*i].r-s[2*i].rs) // a在左子树的右端点连续区间,则要看右子树的左端点长度+左子树右端点连续长度+a为右端点的连续长度 return find(2*i,a)+find(2*i+1,mid+1); else return find(2*i,a); //a不在左子树的右端点连续区间,则要看a为右端点的连续长度 + a为左端点的连续长度 } else { if(a < s[2*i+1].l+s[2*i+1].ls)//同理 return find(2*i+1,a)+find(2*i,mid); else return find(2*i+1,a); } } int main() { int n, m; while(scanf("%d%d", &n, &m)!= EOF) { char ch[2]; int a; top=0; create(1, 1, n); while(m--) { scanf("%s", ch); if(ch[0]=='D') { scanf("%d", &a); Q[top++]=a; //将a入栈 updata(1, a, 0); } if(ch[0]=='R' && top>0) //栈不空 updata(1, Q[--top], 1); if(ch[0]=='Q') { scanf("%d", &a); int sum=find(1, a); printf("%d\n", sum); } } } return 0; }

浙公网安备 33010602011771号