线段树(单点修改,区间查询)

引入

题 目 描 述 题目描述

10000 10000 10000个正整数,编号从 1 1 1 10000 10000 10000,用 A [ 1 ] A[1] A[1], A [ 2 ] A[2] A[2], A [ 10000 ] A[10000] A[10000]表示。
修改:1.将第 p o s pos pos 个数增加 v a l val val ( 1 < = L < = 10000 ) (1 <= L <= 10000) 1<=L<=10000
统计:1.编号从 L L L R R R 的所有数之和为多少? 其中 1 < = L < = R < = 10000. 1<= L <= R <= 10000. 1<=L<=R<=10000.

方法如下(目前所学):

  1. 修改 A [ p o s ] A[pos] A[pos] —— O ( 1 ) O(1) O(1) ;循环求和 —— O ( n ) O(n) O(n)
  2. 差分:修改 n n n 个元素 O ( n ) O(n) O(n) ; 令 S[0]=0, S[k]=A[1…k] ,那么,A[L…R]的和就等于S[R]-S[L-1] ——— O ( 1 ) O(1) O(1)

从上可以看出 方法一修改快,求和慢。 方法二求和快,修改慢。
那有没有一种结构,修改和求和都比较快呢?答案当然是 线 段 树 线段树 线

线段树的性质

众所周知, 线段树是一棵完全二叉树
设其中一子节点编号为 a a a , 则该节点左儿子编号为 2 a 2a 2a , 其右儿子编号为 2 a + 1 2a+1 2a+1

另:线段树的子节点对应一个区间 顾名思义

线段树的算法

  1. 结构体(tree)
struct Tree
{
	int left,right,val=INT_MAX;	
}tree[maxn<<2];//定义
inline void push_up(int )
{
	tree[o].val=min(tree[left].val,tree[right].val);//根据题目要求
}//向上调整
void build(int k,int l,int r)
{
	tree[k].left=l;
	tree[k].right=r;
	
	if(l==r)
	{
		tree[k].val=read();//tree[k].val=a[l];
		return;
	}
	
	int mid=(l+r)/2;
	int left=k*2,right=k*2+1;
	
	build(left,l,mid);
	build(right,mid+1,r);
	
	push_up();
}//建树
  1. 多个数组
    函数需多加两个引用
int tree[maxn<<2];//定义
inline void build(int o, int l, int r)
{
    if (l == r)
    {
        cin >> tree[o];
        return;
    }
    int mid = (l + r) >> 1;
    build(lw(o), l, mid);
    build(rw(o), mid + 1, r);
    push_up(o);
}//建图
inline void update(int o, int l, int r, int pos, int val);//单点更新
inline int query(int o, int left, int right, int l, int r)//区间查询
                                                          //left和right是目标区间
                                                          //l和r是节点区间(开始一般取1,n)

传送门

线段树的单点修改

1、分解区间

首先是讲原始子区间的分解,假定给定区间 [ L , R ] [L,R] [L,R],只要 L < R L < R L<R ,线段树就会把它继续分裂成两个区间。

首先计算 M = ( L + R ) / 2 M = (L+R)/2 M=(L+R)/2,左子区间为 [ L , M ] [L,M] [L,M],右子区间为 [ M + 1 , R ] [M+1,R] [M+1,R],然后如果子区间不满足条件就继续递归分解。

分解条件:要赋值的位置在 m i d mid mid左则进入区间 [ L , M I D ] [L,MID] [L,MID],否则进入区间 [ M I D + 1 , R ] [MID+1,R] [MID+1,R]

2、判断子区间是否左右相等

  • 若相等,则赋值,return
  • 若不相等,则继续 分 解 区 间 分解区间 (参考1),并向上调整(push_up)

线段树的区间查询

1、继续分解区间

2、判断当前区间是否位于目标区间之内(完全包含)

  • 若包含,则返回ans
  • 若不包含,则继续 分 解 区 间 分解区间 ,并向上调整
	int sum1=INT_MAX,sum2=INT_MAX;
	if(L<=mid)
	{
		sum1=query(left,L,R);
	}
	if(R>mid)
	{
		sum2=query(right,L,R);
	}

最后sum1和sum2取最小值返回


模板

#include<bits/stdc++.h>
using namespace std;

int read()
{
    int s=0; char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (;isdigit(c);c=getchar()) s=s*10+c-'0';
    return s;
}

const int maxn=1e5;
int n,m;
struct Tree
{
	int left,right,val=INT_MAX;	
}tree[maxn*4+99];

void build(int k,int l,int r)
{
	tree[k].left=l;
	tree[k].right=r;
	
	if(l==r)
	{
		tree[k].val=read();
		return;
	}
	
	int mid=(l+r)/2;
	int left=k*2,right=k*2+1;
	
	build(left,l,mid);
	build(right,mid+1,r);
	
	tree[k].val=min(tree[left].val,tree[right].val);
}

void update(int o,int pos,int val)
{
	
	if(tree[o].left==tree[o].right)
	{
		tree[o].val=val;
		return;
	}
	
	int mid=(tree[o].left+tree[o].right)/2;
	int left=o*2,right=o*2+1;
	
	if(pos<=mid)
	{
		update(left,pos,val);
	}
	else
	{
		update(right,pos,val);
	}
	
	tree[o].val=min(tree[left].val,tree[right].val);
}

int query(int o,int L,int R)
{
	
	if(L<=tree[o].left&&R>=tree[o].right)
	{
		return tree[o].val;
	}
	
	int mid=(tree[o].left+tree[o].right)/2;
	int left=o*2,right=o*2+1;
	int sum1=INT_MAX,sum2=INT_MAX;
	
	if(L<=mid)
	{
		sum1=query(left,L,R);
	}
	if(R>mid)
	{
		sum2=query(right,L,R);
	}
	
	return min(sum1,sum2);
}

signed main()
{
	cin>>n>>m;
	build(1,1,n);
	//for(int i=1;i<=n;i++)cout<<summ[i]<<" ";
	//cout<<endl;
	for(int i=1;i<=m;i++)
	{
		int q,l,r;
		q=read();
		l=read();
		r=read();
		if(q==2)
		{
			update(1,l,r);
		}
		else
		{
			cout<<query(1,l,r)<<" ";
		}
	}
}

例题

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a [ i ] > a [ j ] 且 i < j a[i]>a[j] 且 i<j a[i]>a[j]i<j
i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n 个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

输入 #1

6
5 4 2 6 3 1

输出 #1

11

思路

本题采用 值 域 线 段 树 值域线段树 线

tree[o]=val;

tree[o]++;

意为统计某个数 o o o出现的个数

  1. 先对数据进行 离 散 化 离散化 l o w e r b o u n d lower_bound lowerbound
    e g . eg. eg.
    1 2 3 4 9 7
    1 2 3 4 6 5
  2. 再边记录边查询(查询比数 i i i 大的数 j j j 出现的个数)
  3. ans累加

本人AC程序

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define lw(a) a << 1
#define rw(a) (a << 1) | 1

const int maxn = 2e5 + 5;

int n, a[maxn], b[maxn];
int tree[maxn << 2];
int ans;

inline int read()
{
    char c = getchar();
    int x = 0;
    bool f = 0;
    for (; !isdigit(c); c = getchar())
    {
        f ^= !(c ^ 45);
    }
    for (; isdigit(c); c = getchar())
    {
        x = (x << 1) + (x << 3) + (c ^ 48);
    }
    if (f)
    {
        x = -x;
    }
    return x;
}

inline void write(int x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
    {
        write(x / 10);
    }
    putchar(x % 10 + '0');
}

inline bool comp(int x, int y)
{
    return b[x] > b[y];
}

inline void push_up(int x)
{
    tree[x] = tree[lw(x)] + tree[rw(x)];
}

inline void update(int o, int l, int r, int pos)
{
    if (l == r)
    {
        tree[o]++;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
    {
        update(lw(o), l, mid, pos);
    }
    else
    {
        update(rw(o), mid + 1, r, pos);
    }
    push_up(o);
}

inline int query(int o, int left, int right, int l, int r)
{
    if (left <= l && r <= right)
    {
        return tree[o];
    }
    int mid = (l + r) >> 1;
    int ans = 0;
    if (left <= mid)
    {
        ans += query(lw(o), left, right, l, mid);
    }
    if (right > mid)
    {
        ans += query(rw(o), left, right, mid + 1, r);
    }
    return ans;
}

signed main()
{
    n = read();
    for (int i = 1; i <= n; i++)
    {
        b[i] = read();
        a[i] = b[i];
    }
    sort(b + 1, b + n + 1);
    for (int i = 1; i <= n; ++i)
    {
        a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
    }
    //for (int i = 1; i <= n; i++)
    //cout << a[i] << " ";
    //cout << endl;
    for (int i = 1; i <= n; i++)
    {
        update(1, 1, n, a[i]);
        ans += query(1, a[i] + 1, n, 1, n);
    }
    write(ans);
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值