线段树

谈对线段树的理解:

线段树又称(区间树),实质就是:树中节点可表示一个区间,所以称为区间树更合适一些。节点中可添加适当的数据来实现相应的一些操作,每个节点的数据都是建立在左子树和右子树之上。这样做的目的使查找效率从O(n)降为O(log(n))

线段树的模型:


适用与使用线段树解决的问题:某些数据需要按区间进行区分,按区间进行修改,而且需要多次按区间进行查询。例如多次查询第k小元素,多次查询区间内最大最小元素。

推荐题型:

hdoj4217,第一次接触线段树

#include <cstdio>
const int nMax=262144;
struct Node
{
	int l,r;
	int len;
	Node(int l,int r,int len):l(l),r(r),len(len){}
	Node(){}
}node[nMax<<2];//①,这里需要注意一下
int ans;
void build(int l,int r,int rt)
{
	node[rt]=Node(l,r,r-l+1);
	if(l<r)
	{
		int mid=(l+r)>>1;
		build(l,mid,rt<<1);
		build(mid+1,r,rt<<1 | 1);
	}
}
void update(int p,int rt)
{
	if(p > node[rt].len)
	{
		ans = -1;
		return ;
	}
	-- node[rt].len;
	if(node[rt].l == node[rt].r)
	{
		ans = node[rt].l;
		return ;
	}
	else
	{
		if(p <= node[rt << 1].len)
		{
			update(p, rt << 1);
		}
		else
		{
			update(p - node[rt << 1].len,rt << 1 | 1);
		}
	}
}
int main()
{
	freopen("f://data.in","r",stdin);
	int T;
	int n,k,ki;
	scanf("%d",&T);
	for(int cas=1;cas<=T;cas++)
	{
		__int64 sum=0;//②
		scanf("%d %d",&n,&k);
		build(1,n,1);
		for(int i=0;i<k;i++)
		{
			scanf("%d",&ki);
			update(ki,1);
			sum+=ans;
		}
		printf("Case %d: %I64d\n",cas,sum);
	}
	return 0;
}
poj3264,基础题型

/*
题意:N头母牛已排序好,分别输入它们的身高,Q组测试数据,每组测试数据(a,b)输出从第a到b母牛的最高身高和最低身高之差。

AC,一般难度,主要在线段树中数据结构的建立。
新题要尝试自己去解决,不是你不会,而是你不敢去尝试。重在思路。


*/
#include <cstdio>
const int nMax=50010;
int N,Q;
struct Tree
{
	int l,r,min,max;
	Tree(){}
	Tree(int l,int r,int min,int max):l(l),r(r),min(min),max(max){}
}tree[nMax<<2];
int Ni[nMax];
void build(int l,int r,int rt,int &min,int &max)
	//因为自定义数据结构中有了min和max,所以如果不传递min和max也可以实现。根据tree构建时的规律。
{
	if(l==r)
	{
		tree[rt]=Tree(l,l,Ni[l],Ni[l]);
		min=max=Ni[l];
	}
	else
	{
		int min1,max1,min2,max2;
		int mid=(l+r)>>1;
		build(l,mid,rt<<1,min1,max1);
		build(mid+1,r,rt<<1 | 1,min2,max2);
		min=min1<min2?min1:min2;
		max=max1>max2?max1:max2;
		tree[rt]=Tree(l,r,min,max);
	}
}
void search(int l,int r,int rt,int &min,int &max)//①
{
	if(l==r)
	{
		min=max=Ni[l];
		return;
	}
	else if(tree[rt].l==l && tree[rt].r==r)
	{
		min=tree[rt].min;
		max=tree[rt].max;
		return;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(mid<l)
		search(l,r,rt<<1 | 1,min,max);
	else if(mid>=r)
		search(l,r,rt<<1,min,max);
	else
	{
		int min1,min2,max1,max2;
		search(l,mid,rt<<1,min1,max1);
		search(mid+1,r,rt<<1 | 1,min2,max2);
		min=min1<min2?min1:min2;
		max=max1>max2?max1:max2;
	}
}
int main()
{
	//freopen("f://data.in","r",stdin);
	scanf("%d %d",&N,&Q);
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&Ni[i]);
	}
	int min0,max0;
	build(1,N,1,min0,max0);
	for(int i=1;i<=Q;i++)
	{
		int a,b;
		scanf("%d %d",&a,&b);
		int min,max;
		search(a,b,1,min,max);
		printf("%d\n",max-min);
	}
	return 0;
}
hdoj2795,加深理解,还不错

/*
题意:广告栏,高h、宽w,向广告栏里粘贴广告,每个广告高*宽为1*wi,n组数据,分别输入广告宽度wi。粘贴的规律:高度从低到高,依次粘贴,只到这一栏贴不下了,换下一栏。如果到最后都贴不下,输出-1,否则输出所在的栏的高度编号。

AC,WA了N多次,最后发现是①处的错误,There are multiple cases (no more than 40 cases).
其实线段树,问题的关键就是要建立自己的模型,找到最合适的数据结构
方法一:使用成员变量max来表示左子树和右子树最大值。
方法二:使用lmax变量来表示左子树最大值。
方法二是自己想的,但显然方法一比较好一些,方法二的实现也有一些问题。


做完这个题,感觉线段树掌握的差不多了。
*/
#include <cstdio>
const int nMax=200000;
struct Tree
{
	int l,r;
	int max;
	Tree(){max=0;}
	Tree(int l,int r,int max):l(l),r(r),max(max){}
}tree[nMax<<2];
int ans;
int h,w,n;
int fmax(int a,int b)
{
	return a>b?a:b;
}
void build(int l,int r,int rt)
{
	tree[rt]=Tree(l,r,w);
	if(l==r) return;
	int mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1 | 1);
}
void update(int p,int rt)
{
	if(tree[rt].max<p)
	{
		ans=-1;
		return;
	}
	if(tree[rt].l==tree[rt].r)
	{
		ans=tree[rt].l;
		tree[rt].max-=p;
		return;
	}
	if(tree[rt<<1].max>=p)
	{
		update(p,rt<<1);
		tree[rt].max=fmax(tree[rt<<1].max,tree[rt<<1 | 1].max);
	}
	else
	{
		update(p,rt<<1 | 1);
		tree[rt].max=fmax(tree[rt<<1].max,tree[rt<<1 | 1].max);
	}
}
int main()
{
	//freopen("f://data.in","r",stdin);
	while(scanf("%d %d %d",&h,&w,&n)!=EOF)//①
	{
		int N=h<n?h:n;
		build(1,N,1);
		for(int i=0;i<n;i++)
		{
			ans=-1;
			int wi;
			scanf("%d",&wi);
			update(wi,1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

//方法二,WA
#include <cstdio>
const int nMax=200000;
struct Tree
{
	int l,r;
	int lmax;
	Tree(){lmax=0;}
	Tree(int l,int r,int lmax):l(l),r(r),lmax(lmax){}
}tree[nMax<<2];
int ans;
int h,w,n;
void build(int l,int r,int rt)
{
	tree[rt]=Tree(l,l,w);
	if(l==r) return;
	int mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1 | 1);
	tree[rt]=Tree(l,r,w);
}
void update(int p,int l,int r,int rt)
{
	if(l==r)
	{
		if(tree[rt].lmax>=p)
		{
			ans=l;
			tree[rt].lmax-=p;
		}
		return;
	}
	int mid=(l+r)>>1;
	if(p<=tree[rt].lmax)
	{
		update(p,l,mid,rt<<1);
		int lrt=rt<<1;
		int lrrt=lrt<<1 | 1;
		int max1=0,max2=0;
		if(lrt<(nMax<<2)) max1=tree[lrt].lmax;
		if(lrrt<(nMax<<2)) max2=tree[lrrt].lmax;
		tree[rt].lmax=max1>max2?max1:max2;
	}
	else
		update(p,mid+1,r,rt<<1 | 1);
}
int main()
{
	//freopen("f://data.in","r",stdin);
	while(scanf("%d %d %d",&h,&w,&n)!=EOF)
	{
		int N=h<n?h:n;
		build(1,N,1);
		for(int i=0;i<n;i++)
		{
			ans=-1;
			int wi;
			scanf("%d",&wi);
			update(wi,1,N,1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

poj2182,推荐题型。。。

/*
题意:N头母牛,他们都有自己唯一的编号(从1到N),然后输入N-1组数据,每组数据Ni[i]代表这一头母牛之前有多少编号比他小的母牛的个数,其中第一肯定为0,所以直接从第二开始。求这些母球的编号排序情况

思路:线段树实现,这个使用很灵活。从后向前读取数据,第i组数据Ni[i]表示的为剩余数据的第Ni[i]+1小值。发现了第K小问题,所以可以用线段树来实现。

这个题不错,灵活运用线段树。
*/

#include <cstdio>
const int nMax=8010;
int N;
int Ni[nMax],res[nMax];
int ans;
struct Tree
{
	int l,r;
	int num;
	Tree(){num=0;}
	Tree(int l,int r,int num):l(l),r(r),num(num){}
}tree[nMax<<2];
void build(int l,int r,int rt)
{
	if(l==r)
		tree[rt]=Tree(l,r,1);
	else
	{
		int mid=(l+r)>>1;
		build(l,mid,rt<<1);
		build(mid+1,r,rt<<1 | 1);
		tree[rt]=Tree(l,r,tree[rt<<1].num+tree[rt<<1 | 1].num);
	}
}
void search(int p,int rt)
{
	tree[rt].num--;
	if(tree[rt].l==tree[rt].r)
	{
		ans=tree[rt].l;
		return;
	}
	if(p<tree[rt<<1].num)
		search(p,rt<<1);
	else
		search(p-tree[rt<<1].num,rt<<1 | 1);
}
int main()
{
	//freopen("f://data.in","r",stdin);
	scanf("%d",&N);
	Ni[0]=0;
	for(int i=1;i<N;i++)
		scanf("%d",&Ni[i]);
	build(1,N,1);
	for(int i=N-1;i>=0;i--)
	{
		search(Ni[i],1);
		res[i]=ans;
	}
	for(int i=0;i<N;i++)
		printf("%d\n",res[i]);
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值