首页 > 其他分享 >线段树学习总结

线段树学习总结

时间:2023-05-26 17:12:27浏览次数:52  
标签:总结 string int 线段 tree 学习 区间 query

线段树入门

线段树的概念

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。

线段树的操作

Pushup(Pushdown后面再讲)

pushup操作就是从叶结点不断向上回溯,同时不断更新父节点的值。

模板
$\color{blue}{代码点这里}$
inline void pushup(int u)
{
		//求最大值
    tree[u].v = max(tree[u << 1].v, tree[u << 1 | 1].v);
    //求最小值
    tree[u].v = min(tree[u << 1].v, tree[u << 1 | 1].v);
    //求和
    tree[u].v = tree[u << 1].v + tree[u << 1 | 1].v;
}

建树(build操作)

建树即为从根节点不断的向下递归,分出每一个节点(除了叶结点)的左子树和右子树,并进行赋值回溯。

模板
$\color{purple}{代码点这里}$
inline void build(int u, int l, int r)
{
    tree[u].l = l, tree[u].r = r;//更新左端点,右端点
    
    if (l == r) return; //叶结点,回溯
    
    int mid = l + r >> 1; ////中间点——分出左子树和右子树
    build(u << 1, l, mid); //建立左子树
    build(u << 1 | 1, mid + 1, r); //建立右子树
		pushup(u);
}

修改单点(modify操作)

修改单点就是不断向下找到叶结点进行更改,在进行一次Pushup操作,更新父节点

模板
$\color{orange}{代码点这里}$
inline void modify(int u, int x, int c) //将点x改成c
{
    if (tree[u].l == x && tree[u].r == x) tree[u].v = c; //枚举到叶结点
    else 
    {
        int mid = tree[u].l + tree[u].r >> 1;
        if (x <= mid) modify(u << 1, x, c); //说明x在左子树中
        else modify(u << 1 | 1, x, c);//说明x在右子树中

        pushup(u);//向上更新父节点
    }

    return;
}

查询区间(query操作)

查询区间分三种情况:
在这里插入图片描述

  • 树节点的区间被查询的区间完全包含,那么直接返回该节点的值
  • 查询区间的左端点在mid的左边,说明在左子树
  • 查询区间的右端点在mid的右边,说明在右子树
  • 第四种其实就是2,3种的结合,所以2,3中的判断条件都得是if,而不能是else if或else
模板
$\color{green}{代码点这里}$
inline int query(int u, int l, int r)
{
    if (tree[u].l >= l && tree[u].r <= r) return tree[u].v; //第一种

    int mid = tree[u].l + tree[u].r >> 1, v = 0;
    if (l <= mid) v = query(u << 1, l, r); //第二种
    if (r > mid) v = max(v, query(u << 1 | 1, l, r)); //第三种
    return v;
}

到此为止,线段树入门的函数就讲完了,下面我们来举个例子,说明如何应用。

实例讲解(F - Substring of Sorted String)

原题

Problem Statement

You are given a string \(S\) of length \(N\) consisting of lowercase English letters, and \(Q\) queries. Process the queries in order.

Each query is of one of the following two kinds:

  • \(1\) \(x\) \(c\) : replace the \(x\)-th character of S by the character \(c\).
  • \(2\) \(l\) \(r\) : let \(T\) be the string obtained by sorting the characters of \(S\) in ascending order. Print \(Yes\) if the string consisting of the \(l\)-th through \(r\)-th characters of \(S\) is a substring of \(T\); print \(No\) otherwise.

Constraints

  • \(1\leq N\leq10^5\)
  • \(S\) is a string of length
  • \(N\) consisting of lowercase English letters.
  • \(1\leq Q\leq 10^5\)
  • For each query of the first kind, \(1\leq x\leq N\).
  • For each query of the first kind, \(c\) is a lowercase English letter.
  • For each query of the second kind, \(1\leq l\leq r\leq N\).

Input

The input is given from Standard Input in the following format, where
query \(i​\) denotes the \(i\)-th query:

N  S  Q
query1​
query2​
⋮
queryQ​

Output

Process the queries as instructed in the Problem Statement.

Sample Input 1

6
abcdcf
4
2 1 3
2 2 6
1 5 e
2 2 6

Sample Output 1

Yes
No
Yes
  • In the \(1\)-st query, abccdf is the string \(T\) obtained by sorting the characters of S in ascending order. The string abc, consisting of the \(1\)-st through \(3\)-rd characters of \(S\), is a substring of \(T\), so Yes should be printed.
  • In the \(2\)-nd query, abccdf is the string \(T\) obtained by sorting the characters of S in ascending order. The string bcdcf, consisting of the \(2\)-nd through \(6\)-th characters of \(S\), is not a substring of \(T\), so No should be printed.
  • The \(3\)-rd query sets the 5-th character of S to e, making \(S\) \(abcdef\).
  • In the \(4\)-th query, abcdef is the string \(T\) obtained by sorting the characters of S in ascending order. The string \(bcdef\), consisting of the \(2\)-nd through \(6\)-th characters of \(S\), is a substring of \(T\), so \(Yes\) should be printed.

思路

本题中若想要将两个区间匹配的话,必须满足以下2个条件:

  • 区间内的字母必须是升序
  • 区间内的每个字母(除了左端点和右端点)在区间中出现的个数,必须和在整个字符串中出现的次数相同。

故能得出以下代码:(带详细注释)


代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e6 + 10, M = 3e1 + 10;

int n, q;
int all[M], section[M]; //整体和区间的字母个数
string s;
struct node
{
	int l, r; //注意l, r存的是字母,不是下标
	int cnt[M];//记录该树中每个字母出现的个数
	bool st; //判断是否满足升序条件
}tree[4 * N];

inline void pushup(int u) //由下到上回溯
{
	for (int i = 1; i <= 26; i ++)
		tree[u].cnt[i] = tree[u << 1].cnt[i] + tree[u << 1 | 1].cnt[i]; //父节点字母个数 = 左结点字母个数 + 左结点字母个数
	
	tree[u].st = (tree[u << 1].st & tree[u << 1 | 1].st) & (tree[u << 1].r <= tree[u << 1 | 1].l); //判断是否有节点并且按升序排列
	tree[u].l = tree[u << 1].l, tree[u].r = tree[u << 1 | 1].r; //更新父节点的左右端点
}

inline void build(int u, int tl, int tr) //建树
{
	if (tl == tr) //是一棵叶结点
	{
		tree[u].l = tree[u].r = (s[tl] - 'a' + 1); //左端点,右端点记录为当前字符
		tree[u].cnt[s[tl] - 'a' + 1] ++; //在这棵子树中该字母出现的次数+1
		tree[u].st = 1; //标记当前有一棵树
		return;
	}
	
	int mid = tl + tr >> 1; //中间点——分出左子树和右子树
	build(u << 1, tl, mid);//建立左子树
	build(u << 1 | 1, mid + 1, tr);//建立右子树
	
	pushup(u);//从下向上更新父节点
}

inline void modify(int u, int tl, int tr, int x, char c)
{
	if (tl == tr) //是叶结点
	{
		int hashi = s[x] - 'a' + 1;
		tree[u].cnt[hashi] --; //将当前字符在本棵树中的次数减1
		all[hashi] --; //将当前字符在整个字符串中的次数减1
		//---------------------删除该字母操作---------------------
		hashi = c - 'a' + 1;
		tree[u].cnt[hashi] ++;//将更改后的字符在本棵树中的次数加1
		all[hashi] ++;//将更改后字符在整个字符串中的次数加1
		s[x] = c;
		//---------------------添加该字母操作---------------------
		tree[u].l = tree[u].r = hashi; //更新左端点,右端点
		return;
	}
	
	int mid = tl + tr >> 1;
	if (x <= mid) modify(u << 1, tl, mid, x, c); //说明x在左子树中
	else modify(u << 1 | 1, mid + 1, tr, x, c); //说明x在右子树中
	pushup(u); //由于更新了值,所以要“顺藤摸瓜”地更新父节点
}

inline bool query(int u, int tl, int tr, int l, int r) //查询(是否满足升序条件)
{
	if (tl >= l && tr <= r) //查询的区间包含该节点表示的区间
	{
		for (int i = 1; i <= 26; i ++) section[i] += tree[u].cnt[i]; //记录区间内每个字母出现的次数
		return tree[u].st; //确定是否是一棵节点并且是否满足升序条件
	}
	
	int mid = tl + tr >> 1;
	int left = 1;
	bool flg = 1;
	if (l <= mid)
		flg &= query(u << 1, tl, mid, l, r), left = tree[u << 1].r; 
	if (r > mid)
		flg &= query(u << 1 | 1, mid + 1, tr, l, r) & (left <= tree[u << 1 | 1].l); //若左子树的右边的字母小于右子树的左边的字母,即能判断出升序
	
	return flg;
}

inline bool judge(int l, int r) //判断字母个数相同以及升序
{
	memset(section, 0, sizeof section); //首先每次清空
	
	if (!query(1, 1, n, l, r)) return 0; //若不是升序,不行
	
	int mn = 27, mx = 0; //计算区间最大字母和最小字母
	for (int i = 1; i <= 26; i ++) 
		if (section[i])
			mn = min(mn, i), mx = max(mx, i);
			
	for (int i = mn + 1; i < mx; i ++)
		if (section[i] < all[i])
			return 0; //如果数量不匹配,返回0
			
	return 1;
}

int main()
{
	cin.tie(0);
	ios::sync_with_stdio(0);
	
	cin >> n >> s >> q;
//-------读入--------

	s = ' ' + s; //让s下标从1开始	
	for (int i = 1; i <= n; i ++)
		all[s[i] - 'a' + 1] ++;
		
//-------计算每个字符出现的次数-----------
		
	build(1, 1, n);
//-------建树--------
	
	while (q --)
	{
		int op, t1;
		cin >> op >> t1;
				
//--------读入询问-------------
		
		if (op == 1) //更改操作
		{
			char t2;
			
			cin >> t2;
			
			modify(1, 1, n, t1, t2);
		}
		else //查询操作
		{
			int t2;
			cin >> t2;
			
			bool res = judge(t1, t2);
			
			(res) ? puts("Yes") : puts("No");
		}
	}
	
	return 0;
}

持续更新中……

标签:总结,string,int,线段,tree,学习,区间,query
From: https://www.cnblogs.com/Tiny-konnyaku/p/Segment-Tree.html

相关文章

  • 总结MySQL 的一些知识点:MySQL 连接的使用
    MySQL连接的使用在前几章节中,我们已经学会了如何在一张表中读取数据,这是相对简单的,但是在真正的应用中经常需要从多个数据表中读取数据。本章节我们将向大家介绍如何使用MySQL的JOIN在两个或多个表中查询数据。你可以在SELECT,UPDATE和DELETE语句中使用Mysql的JOI......
  • Windows驱动开发学习记录-使用Inf安装过滤驱动时自动添加注册表相关内容
     做过滤驱动时一般需要在相关class驱动里添加过滤信息,即LowerFilters或者UpperFilters,比如disk类的注册表当前信息,如下图:一个常规的inf文件如下所示:;;USBFilter.inf;[Version]Signature="$WINDOWSNT$"Class=TOASTERClassGuid={B85B7C50-6A01-11d2-B841-00C04FAD517......
  • multimap的学习
    #include<map>#include<iostream>usingnamespacestd;voidtest_multimap(){//构造multimap的测试数据multimap<string,string>example;example.insert(make_pair(string("A"),string("11")));example.insert(make_p......
  • 【前端算法学习】数据结构之“栈”
    JS中最棒的数据结构:数组​ 数组是计算机科学中最常用的数据结构。我们知道,可以在数组的任意位置上删除或添加元素。然而,有时候我们还需要一种在添加或删除元素时有更多控制的数据结构。有两种数据结构类似于数组,但在添加和删除元素时更为可控。它们就是栈和队列。​ 要开始学......
  • 开源工作流WorflowCore学习之工作流简单审核
    在开源趋势下,很多开源的组件在国内,乃至全网都少有案例。为了做这个工作流翻了许多帖子和github的帖子在这里对github ZL.WorflowCoreDemo,和PizzaRestaurantWorkflow-main表示感谢,同时也感谢给博客园的帖子。本案例再利用ZL.WorflowCoreDemo中的项目直接进行新加的。关于如何......
  • RMQ 问题的两种实现办法(线段树查询和稀疏表(Sparse Table表)查询)
    引言RMQ算法(RangeMinimum/MaximumQuery)是静态区间极值查询的高效算法,在各种算法竞赛中常常出现,虽然不会单独拿出来做一个题,但是经常作为题的一部分。依据所需实现的不同性能可以有多种写法,这里主要讲基于线段树和稀疏表(SparseTable)的两种方法。线段树实现RMQ线段树是维护......
  • AT_abc271_c 总结
    题目:AT_abc271_c链接:洛谷,AT,vjudge题意有\(n\)本漫画书,第\(i\)本的有卷数\(a_i\),在看漫画前可以执行若干次操作:将任意两本漫画书换成一本任意卷数的漫画书。一个人会按顺序看漫画的第\(1,2,\dots\)卷,当他手上没有下一卷要读的漫画时,将会停止阅读。问这个人最多可......
  • 学习笔记-第08天-命令合集7
    属性:人的属性:性别,身高,体重,年龄。文件的属性:大小,用户,组,权限,创建时间。[root@localhost~]#stat/etc/hostsFile:‘/etc/hosts’Size:158 Blocks:8IOBlock:4096regularfileDevice:802h/2050d Inode:67109833Links:1Access:(0644/-rw-r--r......
  • 算法学习记录(模拟枚举贪心题单):四舍五入(未AC)
    题目链接https://ac.nowcoder.com/acm/contest/20960/1004题目分析注意当第i位为9是,此时进位就是0,但是0<5,所以就不能再用i+1进行判断了。所以对于这种情况可以再添加一个其他变量。未AC代码//主要解决问题,因为使用i+1去判断是否要进位的//逢9进位后就会变成0,那么第i+1位......
  • 算法学习 | 从几个有趣的故事说起,聊聊里面的算法
    前言提到故事我就来劲头了。一方面,我喜欢读故事、讲故事、搜集故事,另一方面,用讲故事的方式会为学习增加一些趣味性,有兴趣可以帮助坚持下去。下面要介绍的故事,有些大家应该不陌生。我之前有读到过,但是没有认真的研究过,有种熟悉的陌生感。今天分享读了的故事、研究了的解题过程、顺便......