zkw线段树学习笔记

PS:

说起线段树,可谓是一把辛酸泪(尽管我喜欢打树状数组??)。代码较?(似乎是滴),TLE(屡见不鲜),递归建树(!!!),说实在,用(说到底lowbit差了些,你懂的)。

先来一发线段树:

 

#include<iostream>
#include<cstdio>
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int maxn=2e4;
int segtree[maxn*4+10],array[maxn];
void build(int,int,int);
int query(int,int,int,int,int);
void updata(int,int,int,int,int);
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&array[i]);
	}
	build(1,1,n);
	……………………
	return 0;
}
void build(int node,int begin,int end)//node结点,begin为array某子区间首标号,end为某子区间末标号。
{
	if(begin==end)
	{
		segtree[node]=array[begin];//首末相等,直接赋值。
	}
	else
	{
		build(2*node,begin,(begin+end)>>1);//区间长度不为一,左右递归。
		build(2*node,(begin+end)>>1+1,end);
		/*---------------区间min------------*/
		if(segtree[2*node]<=segtree[2*node+1])//max改为?>=?
		{
			segtree[node]=segtree[2*node];
		}
		else
		{
			segtree[node]=segtree[2*node+1];
		}
		/*-------------------------------*/
		
		/*---------------区间和------------*/
		segtree[node]=(segtree[2*node]+segtree[2*node+1]);
		/*---------------------------*/
	}
}
int query(int node,int begin,int end,int left,int right)
{
	int p1,p2;
	if(left>end||right<begin)
	{
		return -1;
	}
	if(left<=end&&right>=begin)
	{
		return segtree[node];
	}
	p1=query(2*node,begin,(begin+end)>>1,left,right);
	p2=query(2*node+1,(begin+end)>>1+1,end,left,right);
	if(p1==-1)
	{
		return p2;
	}
	if(p2==-1)
	{
		return p1;
	}
	if(p1<=p2)
	{
		return p1;
	}
	else
	{
		return p2;
	}
}
void updata(int node,int begin,int end,int pos,int val)
{
	if(begin==end)
	{
		segtree[node]+=val;
		return;
	}
	int m=(left+right)>>1+1;
	if(pos<=m)
		updata(2*node,begin,(begin+end)>>1,pos,val);
	else
		updata(2*node+1,(begin+end)>>1+1,end,pos,val);
	/*----MIN--*/
	segtree[node]=min(segtree[2*node],segtree[2*node+1]);
	/*---------*/
}

 真长(还没写完)!!!

当我在不久前,看到了zkw(不要误会),我的世界豁然开朗,代码王者荣耀归来,多年守望简洁先锋,终于get到zkw神器。(实际上我只玩MC)

 zkw(张昆玮大神):男,身处清华大学。zkw线段树出自《统计的力量》

Total:

zkw线段树

1.建树非常简单。

2.线段树能干的,它都行。(似乎是这样的)

3.更多惊喜等你来发掘……

Step1(建树):

首先,堆式储存是关键。——《统计的力量》

想必不用多说,用是zkw(重口味)的一大特点,如下图:

 

一、化为二进制后不难看出,叶子节点的父节点是它的前缀。———>>也就是说,找父亲只需右移一位(>>)!

二、相反,找父节点的叶子就左移————>>左移一位为左儿子,再+1(或者|1)为右儿子

三、第n层节点个数为2(n-1)

四、Last but not least,最底层节点个数为你实际最多可以操作的数的个数(换个说法,应该可以叫做值域为2的次幂)。!!!(最重要

 有了它们,就可以开始踏上理论化为现实的伟大道路。

 实践开始!

实际上,很多时候数组中数都不是2的次幂,怎么办?————>>直接开2的次幂就行了,多的空间不要了。

————《统计的力量》

一、我们知道,最底层实际上存的是原数组,同时由于堆式储存的特性,序号也是顺序排列的,也就是说————>>当我们需要查询或修改时,只需在原数组序号上加上一个数,设为m吧。

如何求m

for(m=1;m<n;m<<=1);

 实际上,m为最底层所有节点的父节点总数,所以只需设m为1,不断左移,当m>=n时停止。

对于叶子直接输入,对于父节点从叶子(继承?区间和,最大值,最小值……)。

build函数轻松得出:

void build(int n)
{
	for(m=1;m<n;m<<=1);
	//m<<=1;//避免查端点值出错
	for(int i=m+1;i<n+m+1;i++) scanf("%d",&a[i]);//从m+1叶子节点开始,避免查询[1,...]时出错。
	for(int i=m-1;i;--i) a[i]=a[i<<1]+a[i<<1|1];//区间和
/*
for(int i=m-1;i;--i) a[i]=max(a[i<<1],a[i<<1|1]);//最大值
for(int i=m-1;i;--i) a[i]=min(a[i<<1],a[i<<1|1]);//最小值
*/
}

Step2(操作):

 线段树最经典(也就是我们为何用如此……的线段树)的便是查询修改操作了,时间复杂度比较平均,都为O(logn)。

单点修改

首先说说单点修改。

我们都知道,线段树是由上到下遍历的,而zkw由于可以直接找到叶子,是由下到上遍历的,因此避免了许多多余的访问,对于单点修改,我们只需将需要修改的点找到(加上m),并循环应用到自己、父节点直到根节点,方便了许多。

void updata(int pos,int val)
{
	a[pos+=m]+=val;
	while(pos)
	{
		a[pos>>=1]=a[pos<<1]+a[pos<<1|1];
	}
}

单点查询

单点a[q]?直接查[q,q]不就行了嘛。对,这是没错。但我们要将它变得复杂一些(并不是搞笑,说实在的,谁愿意写长篇大论的code),这是为了RMQ做准备,这是我们需要一个神奇的东西——差分,把子节点所存的值维护为与其父节点的差值。

 所以,build函数需要改改:

void build(int n)//区间和
{
	for(;m<n;m<<=1);
	//m<<=1;//避免查端点值出错
	for(int i=m+1;i<n+m+1;i++) scanf("%d",&a[i]);//从m+1叶子节点开始,避免查询[1,...]时出错。
	for(int i=m-1;i;--i) 
	{
		a[i]=min(a[i<<1],a[i<<1|1]);
		a[i<<1]-=a[i],a[i<<1|1]-=a[i];
	}
}

 

此时的查询,只需从叶子节点不断循环加上父节点直到根节点。

 

int point(int x)
{
	int ans=0;
    while(x) 
	{
		ans+=a[x],x>>=1;
	}
	return ans;
}

区间查询 

查询区间和,暂且设区间为[l,r]吧,zkw又一特点,化为(l-1,r+1)开区间计算。

因此,这就是为何输入时要从m+1开始,避免查询[1,...]时出错,若要查询[0,……],下标需加上1。

int query(int l,int r)
{
	int ans=0;
	for(l+=m-1,r+=m+1;l^r^1;l>>=1,r>>=1)
	{
		if(~l&1) ans+=a[l^1];
		if(r&1) ans+=a[r^1];
	}
	return ans;
}

 ~l&1,意思是是否为儿子,对于兄弟节点来说,最低位为0或1,0为左儿子,1为右儿子,对于左端点 l 来说,我们只需向右合并更新ans(加上兄弟节点,也就是右节点,l^1),而不管其左边。

r&1,同理,意思是是否为儿子。

每次循环后移向其父节点继续操作,出口为l^r^1,为什么?若lr不为同一点或兄弟节点l^r^1一定为true,否则在为同一点或兄弟节点时跳出循环。

求max

将更新时+改为max就行了。

int query(int l,int r)
{
	int ans=0;
	for(l+=m-1,r+=m+1;l^r^1;l>>=1,r>>=1)
	{
		if(~l&1) ans=max(a[l^1],ans);
		if(r&1) ans=max(a[r^1],ans);
	}
	/************
	int mid=max(l,r);
	while(mid)
	{
		ans+=a[mid>>=1];//差分时定要记住回归,不要只将差值max输出
	}
	*************/
	return ans;
}

求min

将上述max改为min

区间修改

 最简单的无非区间加减(但我也只说这个),和区间查询一样,只要将更新ans改为a[i]+=val,加上差分回归(需用差分)。

void intervalup(int l,int r,int val)
{
	int tmp=0;
	for(l+=m-1,r+=m+1;l^r^1;l>>=1,r>>=1)
	{
		if(~l&1) a[l^1]+=val;
		if(r&1) a[r^1]+=val;
		tmp=min(a[l],a[l^1]),a[l]-=tmp,a[l^1]-=tmp,a[l>>1]+=tmp;
		tmp=min(a[r],a[r^1]),a[r]-=tmp,a[r^1]-=tmp,a[r>>1]+=tmp;
	}
	while(l) tmp=min(a[l],a[l^1]),a[l]-=tmp,a[l^1]-=tmp,a[l>>=1]+=tmp;
}

Last:

 以上便是zkw的最基本内容,简单也不简单,最后,来一串zkw类

//powered by:spaceskynet 2017-03-06
const int maxn=1e5;
class zkw
{
public:
	zkw()
	{
		m=1;
	}
	void build(int n)//区间和
	{
		for(;m<n;m<<=1);
		//m<<=1;//避免查端点值出错
		for(int i=m+1;i<n+m+1;i++) scanf("%d",&a[i]);//从m+1叶子节点开始,避免查询[1,...]时出错。
		for(int i=m-1;i;--i) a[i]=a[i<<1]+a[i<<1|1];
		/**差分建树
		for(int i=m-1;i;--i)
		{
			a[i]=min(a[i<<1],a[i<<1|1]);
			a[i<<1]-=a[i],a[i<<1|1]-=a[i];
		}
		**/
	}
	int query(int l,int r)
	{
		int ans=0;
		for(l+=m-1,r+=m+1;l^r^1;l>>=1,r>>=1)
		{
			if(~l&1) ans+=a[l^1];
			if(r&1) ans+=a[r^1];
		}
		/************
		int mid=max(l,r);
		while(mid)
		{
			ans+=a[mid>>=1];//差分时定要记住回归,不要只将差值max输出
		}
		*************/
		return ans;
	}
	void updata(int pos,int val)
	{
		a[pos+=m]+=val;
		while(pos)
		{
			a[pos>>=1]=a[pos<<1]+a[pos<<1|1];
		}
	}
	void intervalup(int l,int r,int val)
	{
		int tmp=0;
		for(l+=m-1,r+=m+1;l^r^1;l>>=1,r>>=1)
		{
			if(~l&1) a[l^1]+=val;
			if(r&1) a[r^1]+=val;
			tmp=min(a[l],a[l^1]),a[l]-=tmp,a[l^1]-=tmp,a[l>>1]+=tmp;
			tmp=min(a[r],a[r^1]),a[r]-=tmp,a[r^1]-=tmp,a[r>>1]+=tmp;
		}
		while(l) tmp=min(a[l],a[l^1]),a[l]-=tmp,a[l^1]-=tmp,a[l>>=1]+=tmp;
	}
	int point(int x)//差分单点查询
	{
		int ans=0;
		while(x) 
		{
			ans+=a[x],x>>=1;
		}
		return ans;
	}
private:
	int a[2*maxn+2],m;
};

 

转载于:https://www.cnblogs.com/spaceskynet/p/6550942.html

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值