线段树入门(poj 3274 3468 2528)

概念:

在一类问题中,我们需要经常处理可以映射在一个坐标轴上的一些固定线段,例如说映射在OX轴上的线段。由于线段是可以互相覆盖的,有时需要动态地取线段的并,例如取得并区间的总长度,或者并区间的个数等等。一个线段是对应于一个区间的,因此线段树也可以叫做区间树。

线段树常用于区间多次插入查询,经常改变数据。

而线段树的核心在于如何设计一个节点的信息

这里针对线段树的应用有三个方面:

1. 每次查询一个区间的最大差值(结点存放这个区间的最大最小值 3274)

2.不断修改区间内的数值,并查询区间的和

这里涉及到了修改,如果每次一修改就递归修改到叶子,效率非常低。所以在结点中设计一个inc,即加量。这个区间每个元素都应该添加的。只有当查询到了这个区间的时候才会递归修改下部的数值。

3.线段离散化。

离散化就是压缩区间,使原有的长区间映射到新的短区间,但是区间压缩前后的覆盖关系不变。举个例子:

有一条1到10的数轴(长度为9),给定4个区间[2,4] [3,6] [8,10] [6,9],覆盖关系就是后者覆盖前者,每个区间染色依次为 1 2 3 4。

现在我们抽取这4个区间的8个端点,2 4 3 6 8 10 6 9

然后删除相同的端点,这里相同的端点为6,则剩下2 4 3 6 8 10 9

对其升序排序,得2 3 4 6 8 9 10

然后建立映射

2     3     4     6     8     9   10

↓     ↓      ↓     ↓     ↓     ↓     ↓

1     2     3     4     5     6     7

那么新的4个区间为 [1,3] [2,4] [5,7] [4,6],覆盖关系没有被改变。新数轴为1到7,即原数轴的长度从9压缩到6,显然构造[1,7]的线段树比构造[1,10]的线段树更省空间,搜索也更快,但是求解的结果却是一致的

离散化时有一点必须要注意的,就是必须先剔除相同端点后再排序,这样可以减少参与排序元素的个数,节省时间。

数据存储:

链式或者数组形式,因为是类完全二叉树所以可以用数组存储,且方便定位。假设区间元素有N,则总结点个数为2^(logN+1),实测发现一般4*N就够了

POJ 3274

/*
核心函数
1.buildtree :递归生成,自下向上建立。即利用下层数据。孩子结点为loc<<1,loc<<1|1
2.query:这个函数还是很有技巧性,每次查询先分区间类型(包含,无交集,部分涵盖) 无交集直接pass,包含则直接提取信息。部分涵盖则判断与Mid的关系。
看是否可以完全转到另一个分支中去
3.巧用位运算,加速程序。左右孩子分别为Loc<<1,loc<<1|1
*/
#include <stdio.h>
#include <string.h>
#define INF 10000000
#define maxn 200005
#define max(a,b)  (a)>(b)?(a):(b)
#define min(a,b)  (a)<(b)?(a):(b)
struct node
{
	int l,r;
	int mn,mx;
}nodes[3*maxn];
int a[maxn];
int qmin,qmax;

void buildtree(int loc,int l,int r)
{
	int mid;
	nodes[loc].l=l;nodes[loc].r=r;
	if(l==r)
		nodes[loc].mn=nodes[loc].mx=a[l];
	else
	{
		mid=(l+r)>>1;
		buildtree(loc<<1,l,mid);
		buildtree(loc<<1|1,mid+1,r);
		nodes[loc].mn=min(nodes[loc<<1].mn,nodes[loc<<1|1].mn);
		nodes[loc].mx=max(nodes[loc<<1].mx,nodes[loc<<1|1].mx);
	}
}

void query(int loc,int l,int r)
{
	if(nodes[loc].mn>=qmin && nodes[loc].mx<=qmax)
		return;
	if(nodes[loc].l==l && nodes[loc].r==r)
	{
		qmin=min(nodes[loc].mn,qmin);
		qmax=max(nodes[loc].mx,qmax);
	}
	else
	{
		int mid;
		mid=(nodes[loc].l+nodes[loc].r)>>1;
		if(r<=mid) query(loc<<1,l,r);
		else if(l>mid) query(loc<<1|1,l,r);
		else
		query(loc<<1,l,mid),query(loc<<1|1,mid+1,r);
	}
}
int main()
{
	int N,C;
	int e,r;
	while(scanf("%d%d",&N,&C)!=EOF)
	{
		int i;
		for(i=1;i<=N;i++)
			scanf("%d",a+i);
		buildtree(1,1,N);
		for(i=0;i<C;i++)
		{
			scanf("%d%d",&e,&r);
			qmax=-INF;qmin=INF;
			query(1,e,r);
			printf("%d\n",qmax-qmin);
		}
	}
	return 0;
}

POJ 3468

延迟更新

/*
Add函数每次添加都只添加到该区间,并不递归全部更新(延缓更新)
在查询的过程中才会更新
*/
#include <stdio.h>
#include <string.h>
#include <iostream>
#define INF 10000000
#define maxn 100000
#define max(a,b)  (a)>(b)?(a):(b)
#define min(a,b)  (a)<(b)?(a):(b)
using namespace std;
struct node
{
	int l,r;
	__int64 sum;
	__int64 inc;
}nodes[3*maxn];//结点设计;左右区间,待增加量,和值
int num[maxn];
int qmin,qmax;
void add(int i,int a,int b,__int64 c)
{
	 if(nodes[i].l==a&&nodes[i].r==b)
    {
        nodes[i].inc+=c;
        return;
    }
    nodes[i].sum+=c*(b-a+1);//运行到这里,表明这个是大区间,包含添加的区间所以sum是他们的个数之和
    int mid=(nodes[i].l+nodes[i].r)>>1;
    if(b<=mid)  add(i<<1,a,b,c);
    else if(a>mid)  add(i<<1|1,a,b,c);
    else
    {
        add(i<<1,a,mid,c);
        add(i<<1|1,mid+1,b,c);
    }
}
__int64 buildtree(int loc,int l,int r)
{
	int mid;
	nodes[loc].l=l;nodes[loc].r=r;
	nodes[loc].inc=0;
	if(l==r)
		return nodes[loc].sum=num[l];
	else
	{
		mid=(l+r)>>1;
		nodes[loc].sum=buildtree(loc<<1,l,mid)+buildtree(loc<<1|1,mid+1,r);
	}
	return nodes[loc].sum;
}

__int64 query(int loc,int l,int r)
{
	__int64 ans=0;
	if(nodes[loc].l==l && nodes[loc].r==r)
	{
		return nodes[loc].sum+(r-l+1)*nodes[loc].inc;
	}
	else
	{
		nodes[loc].sum+=(nodes[loc].r-nodes[loc].l+1)*nodes[loc].inc;
		int mid;
		mid=(nodes[loc].l+nodes[loc].r)>>1;
		add(loc<<1,nodes[loc].l,mid,nodes[loc].inc);//每次都只更新需要的区间,真妙~!
		add(loc<<1|1,mid+1,nodes[loc].r,nodes[loc].inc);
		nodes[loc].inc=0;
		if(r<=mid) ans=query(loc<<1,l,r);
		else if(l>mid) ans=query(loc<<1|1,l,r);
		else
		ans=query(loc<<1,l,mid)+query(loc<<1|1,mid+1,r);
	}
	return ans;
}
int main()
{
    int n,q;
    int i;
    int a,b,c;
    char ch;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        for(i=1;i<=n;i++)  scanf("%d",&num[i]);
        buildtree(1,1,n);
        for(i=1;i<=q;i++)
        {
            cin>>ch;
            if(ch=='C')
            {
                scanf("%d%d%d",&a,&b,&c);
                add(1,a,b,c);
            }
            else
            {
                scanf("%d%d",&a,&b);
                printf("%I64d\n",query(1,a,b));
            }
        }
    }
    return 0;
}     

POj 2528

离散化,剪枝

/*
数据离散化经典使用
这里注意离散化的时候不能因为怕浪费空间就用char,会溢出
离散化映射使用数组内容->数组下标
*/
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define MM 10000000+200
#define maxn 20010
#define max(a,b)  (a)>(b)?(a):(b)
#define min(a,b)  (a)<(b)?(a):(b)
using namespace std;
struct node
{
	int l,r;
	int color;
}nodes[4*maxn];
int num[maxn];
int qmin,qmax;
int cnt;
int ans;
int a[maxn],b[maxn];
int vis[MM];
void insert(int l,int r,int color,int loc)//从区间关系入手
{
    if(nodes[loc].color==color) return;//颜色一致,不用更新
    if(r<nodes[loc].l || l>nodes[loc].r)
        return;//完全无覆盖
    if(nodes[loc].l>=l && nodes[loc].r<=r)
        {
                nodes[loc].color=color;//已经内含
                return;
        } //部分覆盖
    if(nodes[loc].color>=0)
	{
		nodes[loc<<1].color=nodes[loc<<1|1].color=nodes[loc].color;
		nodes[loc].color=-1;
	}
    //此节点为多色,先更新孩子的颜色,再为-1;
	insert(l,r,color,loc<<1);
	insert(l,r,color,loc<<1|1);
    /*int mid=(nodes[loc].l+nodes[loc].r)>>1;
    if(r<=mid) insert(l,r,color,loc<<1);
    else if(l>mid) insert(l,r,color,loc<<1|1);
    else
    {
        insert(l,mid,color,loc<<1);
        insert(mid+1,r,color,loc<<1|1);
    }*/
}
void buildtree(int loc,int l,int r)
{
	int mid;
	nodes[loc].l=l;nodes[loc].r=r;
	nodes[loc].color=0;
	if(l==r)
		return ;
	else
	{
		mid=(l+r)>>1;
		buildtree(loc<<1,l,mid);
		buildtree(loc<<1|1,mid+1,r);
	}
}

void dfs(int loc)
{
    if(nodes[loc].color==0)
        return;
    else if(nodes[loc].color>0 && vis[nodes[loc].color]==0)
        ans++,vis[nodes[loc].color]=1;
    else if(nodes[loc].color==-1)
    {

        dfs(loc<<1);
        dfs(loc<<1|1);
    }
}
int main()
{
    int n,q,i;
#ifndef ONLINE_JUDGE//提交的时候把这段删掉
	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
#endif
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&q);
        cnt=0;ans=0;
        memset(vis,0,sizeof(vis));
        for( i=0;i<q;i++)
        {
            scanf("%d%d",a+i,b+i);
            if(vis[a[i]]==0)
                vis[a[i]]=1,num[cnt++]=a[i];
            if(vis[b[i]]==0)
                vis[b[i]]=1,num[cnt++]=b[i];
        }
        sort(num,num+cnt);
        int hash=0;

        for( i=0;i<cnt;i++)
            vis[num[i]]=++hash;//hash映射,
        buildtree(1,1,hash);
        for(i=0;i<q;i++)
            insert(vis[a[i]],vis[b[i]],i+1,1);
        memset(vis,0,sizeof(vis));
        dfs(1);
        printf("%d\n",ans);

    }
    return 0;
}
时间: 2024-10-29 10:46:46

线段树入门(poj 3274 3468 2528)的相关文章

线段树+离散化 POJ 2528 Mayor&#39;s posters

题目传送门 题意:在一面墙上贴海报,有先后顺序,问最后有多少张不同的海报(指的是没被覆盖或者只是部分覆盖的海报) 分析:这题数据范围很大,直接搞超时+超内存,需要离散化:离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来

线段树入门小结

QUE:线段树? 称谓: 从刘汝佳的书中得知,"这种数据结构在学术界没有统一的术语,但线段树是最常见的叫法.其他叫法包括区间树(interval tree).范围树(range tree)等,但这些属于在特定的场合(如计算几何)中有着特殊的意义".怎么叫看读者的心情,以下统一用线段树称呼. 先来作一些了解: 线段树是一棵二叉树,它的左右儿子也都是一棵线段树.(定义) 线段树也叫区间树,为什么叫它区间树呢?因为线段树是一种基于区间的数据结构. 线段树的每个节点代表一个区间 [L,R],其

线段树入门(Billboard)

Billboard Time Limit:8000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Description At the entrance to the university, there is a huge rectangular billboard of size h*w (h is its height and w is its width). The board is the place where

线段树入门(I Hate It)

I Hate It Time Limit:3000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问.当然,老师有时候需要更新某位同学的成绩. Input 本题目包含多组测试,请处理到文件结束. 在每个测试的第一行,

《数据结构》线段树入门(二)

今天继续介绍——线段树之延迟标记 接上期<数据结构>线段树入门(一):http://www.cnblogs.com/shadowland/p/5870339.html 在上期介绍了线段树的最基本内容(线段树单点修改,区间查询),这次将介绍:区间修改,区间查询. Question: 给你N个数,有两种操作: 1:给区间[a,b]的所有数增加X 2:询问区间[a,b]的数的和. 输入描述: 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,每行表示操作的个数,如果第一数是1,后接3个正

线段树入门理解

在复习算法至分治法时,书本上主要介绍了合并排序和快速排序,较为简单.特拓展简单学习一个应用了分治法的算法结构--线段树. acm刷题时遇到许多连续区间的动态查询问题,例如求取某一区间上元素之和.求取某一区间上元素的最大值,此时如果使用一般的方法求解会使得时间超出要求.此时需要使用到线段树,其主要用于高效解决连续区间的动态查询问题. 线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN),从而大大减少耗时

hdu1166敌兵布阵&amp;&amp;hdu1754I Hate It(线段树入门)

单点更新是最最基础的线段树,只更新叶子节点,然后把信息用pushup这个函数更新上来. http://acm.hdu.edu.cn/showproblem.php?pid=1166 update单点更新,query区域求和. #include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #define N 200001 using namespace std; s

hdu 1754 I Hate It(线段树入门)

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 41510    Accepted Submission(s): 16458 Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师

线段树入门(更新单个节点)

很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问.当然,老师有时候需要更新某位同学的成绩. Input本题目包含多组测试,请处理到文件结束. 在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目. 学生ID编号分别从1编到N. 第二行包含N个整数,代表这N个学生的初始成绩,